From 45a3ba09fc2b04e73241702e4cff3648abe5a68f Mon Sep 17 00:00:00 2001 From: Danilo Cardoso Date: Thu, 11 Jul 2024 14:33:48 +0200 Subject: [PATCH 01/12] Updating model for Refund Fail webhook and small improvements (#52) * fix: updating model for refund fail and small improvements * fix: updating code owners * refactor: hmac validator for better debugging purposes --------- Co-authored-by: daniloc --- .github/CODEOWNERS | 2 +- .../main/default/classes/AdyenConstants.cls | 21 ++++--- .../classes/AdyenConstants.cls-meta.xml | 2 +- .../main/default/classes/HMACValidator.cls | 53 +++++++++++------ .../classes/HMACValidator.cls-meta.xml | 2 +- .../default/classes/HMACValidatorTest.cls | 59 ++++++++++++------- .../classes/HMACValidatorTest.cls-meta.xml | 2 +- .../classes/NotificationRequestItem.cls | 3 - .../NotificationRequestItem.cls-meta.xml | 2 +- 9 files changed, 88 insertions(+), 58 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f89d4af..b936c0f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @amihajlovski @dcardos @shanikantsingh @shubhamvijaivargiya @zenit2001 +* @amihajlovski @dcardos @shanikantsingh @shubhamk67 @shubhamvijaivargiya @zenit2001 diff --git a/force-app/main/default/classes/AdyenConstants.cls b/force-app/main/default/classes/AdyenConstants.cls index 282fbc5..fa7391a 100644 --- a/force-app/main/default/classes/AdyenConstants.cls +++ b/force-app/main/default/classes/AdyenConstants.cls @@ -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'; @@ -66,5 +69,5 @@ public with sharing class AdyenConstants { 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; } \ No newline at end of file diff --git a/force-app/main/default/classes/AdyenConstants.cls-meta.xml b/force-app/main/default/classes/AdyenConstants.cls-meta.xml index b1a915c..f5e18fd 100644 --- a/force-app/main/default/classes/AdyenConstants.cls-meta.xml +++ b/force-app/main/default/classes/AdyenConstants.cls-meta.xml @@ -1,5 +1,5 @@ - 59.0 + 60.0 Active diff --git a/force-app/main/default/classes/HMACValidator.cls b/force-app/main/default/classes/HMACValidator.cls index d7c619c..0cbe2d2 100644 --- a/force-app/main/default/classes/HMACValidator.cls +++ b/force-app/main/default/classes/HMACValidator.cls @@ -1,13 +1,28 @@ -@namespaceAccessible +@NamespaceAccessible public with sharing class HMACValidator { private static final String HMAC_SHA256_ALGORITHM = 'HmacSHA256'; private static final String DATA_SEPARATOR = ':'; - - @namespaceAccessible + private final NotificationRequestItem notificationRequestItem; + private final String hmacKey; + + 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 payloadParts = new List{ notificationRequestItem.pspReference, notificationRequestItem.originalReference, @@ -18,11 +33,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); @@ -31,27 +46,27 @@ 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, hmacSignature); + return compareHMAC(calculatedSignature, merchantSignature); } } \ No newline at end of file diff --git a/force-app/main/default/classes/HMACValidator.cls-meta.xml b/force-app/main/default/classes/HMACValidator.cls-meta.xml index b1a915c..f5e18fd 100644 --- a/force-app/main/default/classes/HMACValidator.cls-meta.xml +++ b/force-app/main/default/classes/HMACValidator.cls-meta.xml @@ -1,5 +1,5 @@ - 59.0 + 60.0 Active diff --git a/force-app/main/default/classes/HMACValidatorTest.cls b/force-app/main/default/classes/HMACValidatorTest.cls index 5d39396..17e0b3b 100644 --- a/force-app/main/default/classes/HMACValidatorTest.cls +++ b/force-app/main/default/classes/HMACValidatorTest.cls @@ -1,7 +1,7 @@ -@isTest +@IsTest private class HMACValidatorTest { - @isTest + @IsTest static void testCalculateHMAC() { NotificationRequestItem requestItem = new NotificationRequestItem(); requestItem.amount = new Amount(); @@ -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(); @@ -39,19 +39,19 @@ 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{ 'hmacSignature' => expectedHmac }; - Boolean isValid = validator.validateHMAC(requestItem, hmacKey); + Boolean isValid = validator.validateHMAC(); Assert.isTrue(isValid); } - @isTest + @IsTest static void testInvalidHMAC() { NotificationRequestItem requestItem = new NotificationRequestItem(); @@ -73,14 +73,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(); @@ -95,22 +95,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')); + } + // given no hmac key + try { // when + validator = new HMACValidator(requestItem, null); 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 merchant signature + try { // when + validator = new HMACValidator(requestItem, hmacKey); + validator.validateHMAC(); + Assert.fail(); + } catch(HMACValidator.HmacValidationException ex) { // then + Assert.isTrue(ex.getMessage().containsIgnoreCase('Missing')); } } } diff --git a/force-app/main/default/classes/HMACValidatorTest.cls-meta.xml b/force-app/main/default/classes/HMACValidatorTest.cls-meta.xml index b1a915c..f5e18fd 100644 --- a/force-app/main/default/classes/HMACValidatorTest.cls-meta.xml +++ b/force-app/main/default/classes/HMACValidatorTest.cls-meta.xml @@ -1,5 +1,5 @@ - 59.0 + 60.0 Active diff --git a/force-app/main/default/classes/NotificationRequestItem.cls b/force-app/main/default/classes/NotificationRequestItem.cls index 39aebbb..01b0ed0 100644 --- a/force-app/main/default/classes/NotificationRequestItem.cls +++ b/force-app/main/default/classes/NotificationRequestItem.cls @@ -27,9 +27,6 @@ public with sharing class NotificationRequestItem { @NamespaceAccessible public String originalReference {get;set;} - @NamespaceAccessible - public String paymentPspReference {get;set;} - @NamespaceAccessible public String pspReference {get;set;} diff --git a/force-app/main/default/classes/NotificationRequestItem.cls-meta.xml b/force-app/main/default/classes/NotificationRequestItem.cls-meta.xml index b1a915c..f5e18fd 100644 --- a/force-app/main/default/classes/NotificationRequestItem.cls-meta.xml +++ b/force-app/main/default/classes/NotificationRequestItem.cls-meta.xml @@ -1,5 +1,5 @@ - 59.0 + 60.0 Active From 96f1831a3f7b69db30f3eeb1f8dd79a0dad88539 Mon Sep 17 00:00:00 2001 From: Danilo Cardoso Date: Fri, 12 Jul 2024 15:31:38 +0200 Subject: [PATCH 02/12] fix: removing AdyenClient since it is not part of the model (#53) Co-authored-by: daniloc --- .../main/default/classes/AdyenClient.cls | 44 ------------------- .../main/default/classes/AdyenClientTest.cls | 17 ------- 2 files changed, 61 deletions(-) delete mode 100644 force-app/main/default/classes/AdyenClient.cls delete mode 100644 force-app/main/default/classes/AdyenClientTest.cls diff --git a/force-app/main/default/classes/AdyenClient.cls b/force-app/main/default/classes/AdyenClient.cls deleted file mode 100644 index caec355..0000000 --- a/force-app/main/default/classes/AdyenClient.cls +++ /dev/null @@ -1,44 +0,0 @@ -/* -* Adyen Checkout API -* Adyen Checkout API provides a simple and flexible way to initiate and authorise online payments. -* You can use the same integration for payments made with cards (including 3D Secure), mobile wallets, and local payment methods (for example, iDEAL and Sofort). -* This API reference provides information on available endpoints and how to interact with them. To learn more about the API, visit [Checkout documentation](https://docs.adyen.com/checkout).\n\n## Authentication\nEach request to the Checkout API must be signed with an API key. For this, obtain an API Key from your Customer Area, as described in [How to get the API key](https://docs.adyen.com/user-management/how-to-get-the-api-key). Then set this key to the `X-API-Key` header value, for example:\n\n```\ncurl\n-H "Content-Type: application/json" \\n-H "X-API-Key: Your_Checkout_API_key" \\n...\n```\nNote that when going live, you need to generate a new API Key to access the [live endpoints](https://docs.adyen.com/development-resources/live-endpoints).\n\n## Versioning\nCheckout API supports versioning of its endpoints through a version suffix in the endpoint URL. This suffix has the following format: "vXX", where XX is the version number.\n\nFor example:\n```\nhttps://checkout-test.adyen.com/v64/payments\n``` -* -* Contact: support@adyen.com -* -*/ - -@namespaceAccessible -public with sharing class AdyenClient { - - @namespaceAccessible - public AdyenConfig config; - - @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){ - HttpRequest httpRequest = createHttpRequest(config, request); - return sendRequest(httpRequest); - } - - private HttpResponse sendRequest(HttpRequest request){ - commercepayments.PaymentsHttp httpClient = new commercepayments.PaymentsHttp(); - return httpClient.send(request); - } - - private HttpRequest createHttpRequest(AdyenConfig config, String request){ - HttpRequest httpReq = new HttpRequest(); - httpReq.setEndpoint(config.getEndpoint()); - httpReq.setMethod('POST'); - httpReq.setHeader('X-API-KEY', config.getApiKey()); - httpReq.setHeader('Content-Type', 'application/json'); - httpReq.setBody(request); - return httpReq; - } -} \ No newline at end of file diff --git a/force-app/main/default/classes/AdyenClientTest.cls b/force-app/main/default/classes/AdyenClientTest.cls deleted file mode 100644 index d6b3e69..0000000 --- a/force-app/main/default/classes/AdyenClientTest.cls +++ /dev/null @@ -1,17 +0,0 @@ -@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(){ - Test.setMock(HttpCalloutMock.class, new ApiLibMock.AdyenPaymentsSuccessMock()); - AdyenClient testADNC = new AdyenClient('37ufgu3iyrfhed','https://testendpoint.com'); - Test.startTest(); - HttpResponse res = testADNC.request(testADNC.config,'testrequeststring'); - Test.stopTest(); - System.assert(res!=null,'This is from AdyenClientTest'); - } -} \ No newline at end of file From 0c9b09bba975392d385daaa1479af9eb5993ef46 Mon Sep 17 00:00:00 2001 From: Danilo Cardoso Date: Wed, 17 Jul 2024 13:24:01 +0200 Subject: [PATCH 03/12] Remove adyen client metadata (#54) * fix: removing AdyenClient since it is not part of the model * fix: removing AdyenClient metadata file. --------- Co-authored-by: daniloc --- force-app/main/default/classes/AdyenClient.cls-meta.xml | 5 ----- force-app/main/default/classes/AdyenClientTest.cls-meta.xml | 5 ----- force-app/main/default/classes/AdyenConstants.cls | 2 +- sfdx-project.json | 2 +- 4 files changed, 2 insertions(+), 12 deletions(-) delete mode 100644 force-app/main/default/classes/AdyenClient.cls-meta.xml delete mode 100644 force-app/main/default/classes/AdyenClientTest.cls-meta.xml diff --git a/force-app/main/default/classes/AdyenClient.cls-meta.xml b/force-app/main/default/classes/AdyenClient.cls-meta.xml deleted file mode 100644 index dd61d1f..0000000 --- a/force-app/main/default/classes/AdyenClient.cls-meta.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - 52.0 - Active - diff --git a/force-app/main/default/classes/AdyenClientTest.cls-meta.xml b/force-app/main/default/classes/AdyenClientTest.cls-meta.xml deleted file mode 100644 index dd61d1f..0000000 --- a/force-app/main/default/classes/AdyenClientTest.cls-meta.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - 52.0 - Active - diff --git a/force-app/main/default/classes/AdyenConstants.cls b/force-app/main/default/classes/AdyenConstants.cls index fa7391a..aebac48 100644 --- a/force-app/main/default/classes/AdyenConstants.cls +++ b/force-app/main/default/classes/AdyenConstants.cls @@ -62,7 +62,7 @@ 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 diff --git a/sfdx-project.json b/sfdx-project.json index 3499e23..f79354d 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -12,7 +12,7 @@ "name": "adyen-apex-api-lib-2GP", "namespace": "adyen_payment", "sfdcLoginUrl": "https://login.salesforce.com", - "sourceApiVersion": "59.0", + "sourceApiVersion": "60.0", "packageAliases": { "API Library Apex Adyen": "0Ho4T0000000043SAA", "API Library Apex Adyen@0.1.0-1": "04t4T000001VvbqQAC", From 181fcb2389c0340295502f9210d08b4f7a748b70 Mon Sep 17 00:00:00 2001 From: daniloc Date: Fri, 19 Jul 2024 09:37:10 +0200 Subject: [PATCH 04/12] task: releasing v3.2.0 version. --- force-app/main/default/classes/HMACValidator.cls | 1 + sfdx-project.json | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/force-app/main/default/classes/HMACValidator.cls b/force-app/main/default/classes/HMACValidator.cls index 0cbe2d2..6ecb192 100644 --- a/force-app/main/default/classes/HMACValidator.cls +++ b/force-app/main/default/classes/HMACValidator.cls @@ -5,6 +5,7 @@ public with sharing class HMACValidator { private final NotificationRequestItem notificationRequestItem; private final String hmacKey; + @NamespaceAccessible public HMACValidator(NotificationRequestItem notificationRequestItem, String hmacKey) { if (notificationRequestItem == null) { throw new HmacValidationException('Invalid notification request item'); diff --git a/sfdx-project.json b/sfdx-project.json index f79354d..6487b55 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -4,8 +4,8 @@ "path": "force-app", "default": true, "package": "API Library Apex Adyen", - "versionName": "ver 3.1", - "versionNumber": "3.1.1.NEXT", + "versionName": "version 3.2", + "versionNumber": "3.2.0.NEXT", "ancestorVersion": "HIGHEST" } ], @@ -21,6 +21,7 @@ "API Library Apex Adyen@3.0.0-0": "04t4T000001y7BWQAY", "API Library Apex Adyen@3.0.1-1": "04tRP0000000A5pYAE", "API Library Apex Adyen@3.1.0-1": "04tRP0000000D25YAE", - "API Library Apex Adyen@3.1.1-1": "04tRP0000000XGvYAM" + "API Library Apex Adyen@3.1.1-1": "04tRP0000000XGvYAM", + "API Library Apex Adyen@3.2.0-2": "04tRP0000000j5BYAQ" } } \ No newline at end of file From b676ba017d654af3e0dc8d168ede988280417c77 Mon Sep 17 00:00:00 2001 From: daniloc Date: Tue, 30 Jul 2024 10:04:04 +0200 Subject: [PATCH 05/12] fix: undeleting classes to avoid OMS upgrade blockage --- .../main/default/classes/AdyenClient.cls | 44 +++++++++++++++++++ .../default/classes/AdyenClient.cls-meta.xml | 5 +++ .../main/default/classes/AdyenClientTest.cls | 17 +++++++ .../classes/AdyenClientTest.cls-meta.xml | 5 +++ .../classes/NotificationRequestItem.cls | 3 ++ sfdx-project.json | 2 +- 6 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 force-app/main/default/classes/AdyenClient.cls create mode 100644 force-app/main/default/classes/AdyenClient.cls-meta.xml create mode 100644 force-app/main/default/classes/AdyenClientTest.cls create mode 100644 force-app/main/default/classes/AdyenClientTest.cls-meta.xml diff --git a/force-app/main/default/classes/AdyenClient.cls b/force-app/main/default/classes/AdyenClient.cls new file mode 100644 index 0000000..030c503 --- /dev/null +++ b/force-app/main/default/classes/AdyenClient.cls @@ -0,0 +1,44 @@ +/* +* Adyen Checkout API +* Adyen Checkout API provides a simple and flexible way to initiate and authorise online payments. +* You can use the same integration for payments made with cards (including 3D Secure), mobile wallets, and local payment methods (for example, iDEAL and Sofort). +* This API reference provides information on available endpoints and how to interact with them. To learn more about the API, visit [Checkout documentation](https://docs.adyen.com/checkout).\n\n## Authentication\nEach request to the Checkout API must be signed with an API key. For this, obtain an API Key from your Customer Area, as described in [How to get the API key](https://docs.adyen.com/user-management/how-to-get-the-api-key). Then set this key to the `X-API-Key` header value, for example:\n\n```\ncurl\n-H "Content-Type: application/json" \\n-H "X-API-Key: Your_Checkout_API_key" \\n...\n```\nNote that when going live, you need to generate a new API Key to access the [live endpoints](https://docs.adyen.com/development-resources/live-endpoints).\n\n## Versioning\nCheckout API supports versioning of its endpoints through a version suffix in the endpoint URL. This suffix has the following format: "vXX", where XX is the version number.\n\nFor example:\n```\nhttps://checkout-test.adyen.com/v64/payments\n``` +* +* Contact: support@adyen.com +* +*/ + +@NamespaceAccessible +public with sharing class AdyenClient { + + @NamespaceAccessible + public AdyenConfig config; + + @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) { + HttpRequest httpRequest = createHttpRequest(config, request); + return sendRequest(httpRequest); + } + + private static HttpResponse sendRequest(HttpRequest request) { + CommercePayments.PaymentsHttp httpClient = new CommercePayments.PaymentsHttp(); + return httpClient.send(request); + } + + private static HttpRequest createHttpRequest(AdyenConfig config, String request) { + HttpRequest httpReq = new HttpRequest(); + httpReq.setEndpoint(config.getEndpoint()); + httpReq.setMethod('POST'); + httpReq.setHeader('X-API-KEY', config.getApiKey()); + httpReq.setHeader('Content-Type', 'application/json'); + httpReq.setBody(request); + return httpReq; + } +} \ No newline at end of file diff --git a/force-app/main/default/classes/AdyenClient.cls-meta.xml b/force-app/main/default/classes/AdyenClient.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/force-app/main/default/classes/AdyenClient.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/force-app/main/default/classes/AdyenClientTest.cls b/force-app/main/default/classes/AdyenClientTest.cls new file mode 100644 index 0000000..ebcbe0a --- /dev/null +++ b/force-app/main/default/classes/AdyenClientTest.cls @@ -0,0 +1,17 @@ +@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() { + Test.setMock(HttpCalloutMock.class, new ApiLibMock.AdyenPaymentsSuccessMock()); + AdyenClient testADNC = new AdyenClient('37ufgu3iyrfhed','https://testendpoint.com'); + Test.startTest(); + HttpResponse res = testADNC.request(testADNC.config,'testrequeststring'); + Test.stopTest(); + System.assert(res!=null,'This is from AdyenClientTest'); + } +} \ No newline at end of file diff --git a/force-app/main/default/classes/AdyenClientTest.cls-meta.xml b/force-app/main/default/classes/AdyenClientTest.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/force-app/main/default/classes/AdyenClientTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/force-app/main/default/classes/NotificationRequestItem.cls b/force-app/main/default/classes/NotificationRequestItem.cls index 01b0ed0..39aebbb 100644 --- a/force-app/main/default/classes/NotificationRequestItem.cls +++ b/force-app/main/default/classes/NotificationRequestItem.cls @@ -27,6 +27,9 @@ public with sharing class NotificationRequestItem { @NamespaceAccessible public String originalReference {get;set;} + @NamespaceAccessible + public String paymentPspReference {get;set;} + @NamespaceAccessible public String pspReference {get;set;} diff --git a/sfdx-project.json b/sfdx-project.json index 6487b55..f83fa23 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -22,6 +22,6 @@ "API Library Apex Adyen@3.0.1-1": "04tRP0000000A5pYAE", "API Library Apex Adyen@3.1.0-1": "04tRP0000000D25YAE", "API Library Apex Adyen@3.1.1-1": "04tRP0000000XGvYAM", - "API Library Apex Adyen@3.2.0-2": "04tRP0000000j5BYAQ" + "API Library Apex Adyen@3.2.0-4": "04tRP0000000lgTYAQ" } } \ No newline at end of file From 5be3a57c5439a2e52e416a93f745f95f75be1ac9 Mon Sep 17 00:00:00 2001 From: Danilo Cardoso Date: Tue, 30 Jul 2024 14:35:55 +0200 Subject: [PATCH 06/12] fix: undeleting classes to avoid OMS upgrade blockage (#56) Co-authored-by: daniloc --- .../main/default/classes/AdyenClient.cls | 44 +++++++++++++++++++ .../default/classes/AdyenClient.cls-meta.xml | 5 +++ .../main/default/classes/AdyenClientTest.cls | 17 +++++++ .../classes/AdyenClientTest.cls-meta.xml | 5 +++ .../classes/NotificationRequestItem.cls | 3 ++ 5 files changed, 74 insertions(+) create mode 100644 force-app/main/default/classes/AdyenClient.cls create mode 100644 force-app/main/default/classes/AdyenClient.cls-meta.xml create mode 100644 force-app/main/default/classes/AdyenClientTest.cls create mode 100644 force-app/main/default/classes/AdyenClientTest.cls-meta.xml diff --git a/force-app/main/default/classes/AdyenClient.cls b/force-app/main/default/classes/AdyenClient.cls new file mode 100644 index 0000000..030c503 --- /dev/null +++ b/force-app/main/default/classes/AdyenClient.cls @@ -0,0 +1,44 @@ +/* +* Adyen Checkout API +* Adyen Checkout API provides a simple and flexible way to initiate and authorise online payments. +* You can use the same integration for payments made with cards (including 3D Secure), mobile wallets, and local payment methods (for example, iDEAL and Sofort). +* This API reference provides information on available endpoints and how to interact with them. To learn more about the API, visit [Checkout documentation](https://docs.adyen.com/checkout).\n\n## Authentication\nEach request to the Checkout API must be signed with an API key. For this, obtain an API Key from your Customer Area, as described in [How to get the API key](https://docs.adyen.com/user-management/how-to-get-the-api-key). Then set this key to the `X-API-Key` header value, for example:\n\n```\ncurl\n-H "Content-Type: application/json" \\n-H "X-API-Key: Your_Checkout_API_key" \\n...\n```\nNote that when going live, you need to generate a new API Key to access the [live endpoints](https://docs.adyen.com/development-resources/live-endpoints).\n\n## Versioning\nCheckout API supports versioning of its endpoints through a version suffix in the endpoint URL. This suffix has the following format: "vXX", where XX is the version number.\n\nFor example:\n```\nhttps://checkout-test.adyen.com/v64/payments\n``` +* +* Contact: support@adyen.com +* +*/ + +@NamespaceAccessible +public with sharing class AdyenClient { + + @NamespaceAccessible + public AdyenConfig config; + + @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) { + HttpRequest httpRequest = createHttpRequest(config, request); + return sendRequest(httpRequest); + } + + private static HttpResponse sendRequest(HttpRequest request) { + CommercePayments.PaymentsHttp httpClient = new CommercePayments.PaymentsHttp(); + return httpClient.send(request); + } + + private static HttpRequest createHttpRequest(AdyenConfig config, String request) { + HttpRequest httpReq = new HttpRequest(); + httpReq.setEndpoint(config.getEndpoint()); + httpReq.setMethod('POST'); + httpReq.setHeader('X-API-KEY', config.getApiKey()); + httpReq.setHeader('Content-Type', 'application/json'); + httpReq.setBody(request); + return httpReq; + } +} \ No newline at end of file diff --git a/force-app/main/default/classes/AdyenClient.cls-meta.xml b/force-app/main/default/classes/AdyenClient.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/force-app/main/default/classes/AdyenClient.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/force-app/main/default/classes/AdyenClientTest.cls b/force-app/main/default/classes/AdyenClientTest.cls new file mode 100644 index 0000000..ebcbe0a --- /dev/null +++ b/force-app/main/default/classes/AdyenClientTest.cls @@ -0,0 +1,17 @@ +@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() { + Test.setMock(HttpCalloutMock.class, new ApiLibMock.AdyenPaymentsSuccessMock()); + AdyenClient testADNC = new AdyenClient('37ufgu3iyrfhed','https://testendpoint.com'); + Test.startTest(); + HttpResponse res = testADNC.request(testADNC.config,'testrequeststring'); + Test.stopTest(); + System.assert(res!=null,'This is from AdyenClientTest'); + } +} \ No newline at end of file diff --git a/force-app/main/default/classes/AdyenClientTest.cls-meta.xml b/force-app/main/default/classes/AdyenClientTest.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/force-app/main/default/classes/AdyenClientTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/force-app/main/default/classes/NotificationRequestItem.cls b/force-app/main/default/classes/NotificationRequestItem.cls index 01b0ed0..39aebbb 100644 --- a/force-app/main/default/classes/NotificationRequestItem.cls +++ b/force-app/main/default/classes/NotificationRequestItem.cls @@ -27,6 +27,9 @@ public with sharing class NotificationRequestItem { @NamespaceAccessible public String originalReference {get;set;} + @NamespaceAccessible + public String paymentPspReference {get;set;} + @NamespaceAccessible public String pspReference {get;set;} From b2606f1f9b5d416c9524c4f0dd1c0aa04073716d Mon Sep 17 00:00:00 2001 From: daniloc Date: Fri, 19 Jul 2024 09:37:10 +0200 Subject: [PATCH 07/12] task: releasing v3.2.0 version. --- force-app/main/default/classes/HMACValidator.cls | 1 + sfdx-project.json | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/force-app/main/default/classes/HMACValidator.cls b/force-app/main/default/classes/HMACValidator.cls index 0cbe2d2..6ecb192 100644 --- a/force-app/main/default/classes/HMACValidator.cls +++ b/force-app/main/default/classes/HMACValidator.cls @@ -5,6 +5,7 @@ public with sharing class HMACValidator { private final NotificationRequestItem notificationRequestItem; private final String hmacKey; + @NamespaceAccessible public HMACValidator(NotificationRequestItem notificationRequestItem, String hmacKey) { if (notificationRequestItem == null) { throw new HmacValidationException('Invalid notification request item'); diff --git a/sfdx-project.json b/sfdx-project.json index f79354d..6487b55 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -4,8 +4,8 @@ "path": "force-app", "default": true, "package": "API Library Apex Adyen", - "versionName": "ver 3.1", - "versionNumber": "3.1.1.NEXT", + "versionName": "version 3.2", + "versionNumber": "3.2.0.NEXT", "ancestorVersion": "HIGHEST" } ], @@ -21,6 +21,7 @@ "API Library Apex Adyen@3.0.0-0": "04t4T000001y7BWQAY", "API Library Apex Adyen@3.0.1-1": "04tRP0000000A5pYAE", "API Library Apex Adyen@3.1.0-1": "04tRP0000000D25YAE", - "API Library Apex Adyen@3.1.1-1": "04tRP0000000XGvYAM" + "API Library Apex Adyen@3.1.1-1": "04tRP0000000XGvYAM", + "API Library Apex Adyen@3.2.0-2": "04tRP0000000j5BYAQ" } } \ No newline at end of file From 9a716e616821aef792de760226d987439ce08007 Mon Sep 17 00:00:00 2001 From: daniloc Date: Tue, 30 Jul 2024 10:04:04 +0200 Subject: [PATCH 08/12] fix: undeleting classes to avoid OMS upgrade blockage --- sfdx-project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfdx-project.json b/sfdx-project.json index 6487b55..f83fa23 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -22,6 +22,6 @@ "API Library Apex Adyen@3.0.1-1": "04tRP0000000A5pYAE", "API Library Apex Adyen@3.1.0-1": "04tRP0000000D25YAE", "API Library Apex Adyen@3.1.1-1": "04tRP0000000XGvYAM", - "API Library Apex Adyen@3.2.0-2": "04tRP0000000j5BYAQ" + "API Library Apex Adyen@3.2.0-4": "04tRP0000000lgTYAQ" } } \ No newline at end of file From a35618eb1524e1aef8be831ab37fdd1324d2f8cf Mon Sep 17 00:00:00 2001 From: Danilo Cardoso Date: Tue, 30 Jul 2024 15:38:23 +0200 Subject: [PATCH 09/12] fix: backward compatibility for hmac validator class (#57) Co-authored-by: daniloc --- force-app/main/default/classes/HMACValidator.cls | 15 +++++++++++++-- .../main/default/classes/HMACValidatorTest.cls | 3 +++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/force-app/main/default/classes/HMACValidator.cls b/force-app/main/default/classes/HMACValidator.cls index 0cbe2d2..0c831e9 100644 --- a/force-app/main/default/classes/HMACValidator.cls +++ b/force-app/main/default/classes/HMACValidator.cls @@ -2,9 +2,13 @@ public with sharing class HMACValidator { private static final String HMAC_SHA256_ALGORITHM = 'HmacSHA256'; private static final String DATA_SEPARATOR = ':'; - private final NotificationRequestItem notificationRequestItem; - private final String hmacKey; + 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'); @@ -69,4 +73,11 @@ public with sharing class HMACValidator { return compareHMAC(calculatedSignature, merchantSignature); } + + @NamespaceAccessible + public Boolean validateHMAC(NotificationRequestItem notificationRequestItem, String hmacKey) { + this.notificationRequestItem = notificationRequestItem; + this.hmacKey = hmacKey; + return validateHMAC(); + } } \ No newline at end of file diff --git a/force-app/main/default/classes/HMACValidatorTest.cls b/force-app/main/default/classes/HMACValidatorTest.cls index 17e0b3b..5a8e966 100644 --- a/force-app/main/default/classes/HMACValidatorTest.cls +++ b/force-app/main/default/classes/HMACValidatorTest.cls @@ -47,7 +47,10 @@ private class HMACValidatorTest { }; Boolean isValid = validator.validateHMAC(); + Assert.isTrue(isValid); + HMACValidator validator2 = new HMACValidator(); + isValid = validator2.validateHMAC(requestItem, hmacKey); Assert.isTrue(isValid); } From 91857a361b1cae1f14aab31a6e6d8b9eb92dd773 Mon Sep 17 00:00:00 2001 From: daniloc Date: Fri, 19 Jul 2024 09:37:10 +0200 Subject: [PATCH 10/12] task: releasing v3.2.0 version. diff --git a/sfdx-project.json b/sfdx-project.json index f79354d..6487b55 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -4,8 +4,8 @@ "path": "force-app", "default": true, "package": "API Library Apex Adyen", - "versionName": "ver 3.1", - "versionNumber": "3.1.1.NEXT", + "versionName": "version 3.2", + "versionNumber": "3.2.0.NEXT", "ancestorVersion": "HIGHEST" } ], @@ -21,6 +21,7 @@ "API Library Apex Adyen@3.0.0-0": "04t4T000001y7BWQAY", "API Library Apex Adyen@3.0.1-1": "04tRP0000000A5pYAE", "API Library Apex Adyen@3.1.0-1": "04tRP0000000D25YAE", - "API Library Apex Adyen@3.1.1-1": "04tRP0000000XGvYAM" + "API Library Apex Adyen@3.1.1-1": "04tRP0000000XGvYAM", + "API Library Apex Adyen@3.2.0-2": "04tRP0000000j5BYAQ" } } \ No newline at end of file --- sfdx-project.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sfdx-project.json b/sfdx-project.json index f79354d..6487b55 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -4,8 +4,8 @@ "path": "force-app", "default": true, "package": "API Library Apex Adyen", - "versionName": "ver 3.1", - "versionNumber": "3.1.1.NEXT", + "versionName": "version 3.2", + "versionNumber": "3.2.0.NEXT", "ancestorVersion": "HIGHEST" } ], @@ -21,6 +21,7 @@ "API Library Apex Adyen@3.0.0-0": "04t4T000001y7BWQAY", "API Library Apex Adyen@3.0.1-1": "04tRP0000000A5pYAE", "API Library Apex Adyen@3.1.0-1": "04tRP0000000D25YAE", - "API Library Apex Adyen@3.1.1-1": "04tRP0000000XGvYAM" + "API Library Apex Adyen@3.1.1-1": "04tRP0000000XGvYAM", + "API Library Apex Adyen@3.2.0-2": "04tRP0000000j5BYAQ" } } \ No newline at end of file From 5e5d5bcad50326e4eea90e020d718d2dbc13d062 Mon Sep 17 00:00:00 2001 From: daniloc Date: Tue, 30 Jul 2024 10:04:04 +0200 Subject: [PATCH 11/12] fix: undeleting classes to avoid OMS upgrade blockage --- sfdx-project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfdx-project.json b/sfdx-project.json index 6487b55..f83fa23 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -22,6 +22,6 @@ "API Library Apex Adyen@3.0.1-1": "04tRP0000000A5pYAE", "API Library Apex Adyen@3.1.0-1": "04tRP0000000D25YAE", "API Library Apex Adyen@3.1.1-1": "04tRP0000000XGvYAM", - "API Library Apex Adyen@3.2.0-2": "04tRP0000000j5BYAQ" + "API Library Apex Adyen@3.2.0-4": "04tRP0000000lgTYAQ" } } \ No newline at end of file From 835783d84cf8b03a7733e55d3852feeed3306a95 Mon Sep 17 00:00:00 2001 From: daniloc Date: Tue, 30 Jul 2024 15:53:49 +0200 Subject: [PATCH 12/12] fix: new build for backwards compatibility --- sfdx-project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfdx-project.json b/sfdx-project.json index f83fa23..738b5e6 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -22,6 +22,6 @@ "API Library Apex Adyen@3.0.1-1": "04tRP0000000A5pYAE", "API Library Apex Adyen@3.1.0-1": "04tRP0000000D25YAE", "API Library Apex Adyen@3.1.1-1": "04tRP0000000XGvYAM", - "API Library Apex Adyen@3.2.0-4": "04tRP0000000lgTYAQ" + "API Library Apex Adyen@3.2.0-5": "04tRP0000000lwbYAA" } } \ No newline at end of file