From f9567ae630b36df98dbcae9f2d697b167d6de255 Mon Sep 17 00:00:00 2001 From: Aleksandar Mihajlovski Date: Mon, 26 Feb 2024 12:55:18 +0100 Subject: [PATCH 01/21] feat: pr template update (#24) --- .github/pull_request_template.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index daec67c..978693c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,10 +1,12 @@ ## Summary - -**Fixed issue**: \ No newline at end of file + +## Tested scenarios +Description of tested scenarios: + +**Fixed issue**: From 2d2bb1469aea3e37411912a5f15da277866fb351 Mon Sep 17 00:00:00 2001 From: Danilo Cardoso Date: Fri, 24 May 2024 09:08:45 +0200 Subject: [PATCH 02/21] Implementing Unit Test automation (#32) * feat: automating unit-tests * fix: checkout order * fix: debugging * fix: checking out order final * fix: conflicts in admin profile --------- Co-authored-by: daniloc --- .github/workflows/unit-tests.yml | 60 ++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .github/workflows/unit-tests.yml diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..aee953f --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,60 @@ +name: Salesforce CI/CD + +on: + pull_request: + branches: + - '**' + workflow_dispatch: + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + env: + PBO_AUTH_URL: ${{ secrets.PBO_AUTH_URL }} + + steps: + - name: Checkout This Repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install Salesforce CLI + run: npm install @salesforce/cli --global + + - name: Create authentication file from secret + run: echo ${PBO_AUTH_URL} > secret.json + + - name: Authenticate to Dev Hub + run: sf org login sfdx-url -f secret.json --set-default-dev-hub + + - name: Create Scratch Org + run: sf org create scratch -f config/project-scratch-def.json --set-default --alias ScratchOrg --no-namespace -y 1 + + - name: Checkout Apex-Library Repository + uses: actions/checkout@v4 + with: + repository: Adyen/adyen-apex-api-library + ref: develop + path: dependency-repo + + - name: Push Apex Lib Source to Scratch Org + run: | + cd dependency-repo + sf project deploy start --target-org ScratchOrg + + - name: Checkout This Repository Back + uses: actions/checkout@v4 + + - name: Deploy This Repository Code + run: sf project deploy start --target-org ScratchOrg --ignore-conflicts + + - name: Run Apex tests + run: sf apex run test --target-org ScratchOrg --code-coverage -w 5 + + - name: Delete Scratch Org + if: always() + run: sf org delete scratch --target-org ScratchOrg --no-prompt From f36f34df3363702421ca2b116ceaf3bba7d926d6 Mon Sep 17 00:00:00 2001 From: Danilo Cardoso Date: Fri, 24 May 2024 09:10:10 +0200 Subject: [PATCH 03/21] feat: updating code owners (#31) Co-authored-by: daniloc --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ab0169a..f89d4af 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @maassenbas @amihajlovski @shubhamvijaivargiya @dcardos +* @amihajlovski @dcardos @shanikantsingh @shubhamvijaivargiya @zenit2001 From 3178f69764bdf1275888ea80b8bb394b27165b82 Mon Sep 17 00:00:00 2001 From: Danilo Cardoso Date: Mon, 10 Jun 2024 13:23:42 +0200 Subject: [PATCH 04/21] Bug fix: making sure capture reference is unique (#33) * fix: capture reference being duplicated for multiple FO with same value * fix: lint issues * fix: removing unused method --------- Co-authored-by: daniloc --- .../classes/AdyenAsyncAdapter.cls-meta.xml | 2 +- .../default/classes/AdyenCaptureHelper.cls | 4 +- .../classes/AdyenCaptureHelper.cls-meta.xml | 2 +- .../classes/AdyenPaymentHelper.cls-meta.xml | 2 +- .../default/classes/AdyenPaymentUtility.cls | 43 ++++-------- .../classes/AdyenPaymentUtility.cls-meta.xml | 2 +- .../classes/AdyenPaymentUtilityTest.cls | 65 +++++++------------ .../AdyenPaymentUtilityTest.cls-meta.xml | 2 +- 8 files changed, 45 insertions(+), 77 deletions(-) diff --git a/force-app/main/default/classes/AdyenAsyncAdapter.cls-meta.xml b/force-app/main/default/classes/AdyenAsyncAdapter.cls-meta.xml index d75b058..f5e18fd 100644 --- a/force-app/main/default/classes/AdyenAsyncAdapter.cls-meta.xml +++ b/force-app/main/default/classes/AdyenAsyncAdapter.cls-meta.xml @@ -1,5 +1,5 @@ - 51.0 + 60.0 Active diff --git a/force-app/main/default/classes/AdyenCaptureHelper.cls b/force-app/main/default/classes/AdyenCaptureHelper.cls index 928fdd4..65d0718 100644 --- a/force-app/main/default/classes/AdyenCaptureHelper.cls +++ b/force-app/main/default/classes/AdyenCaptureHelper.cls @@ -2,7 +2,7 @@ public with sharing class AdyenCaptureHelper { /** * invoked by handleFulfillmentOrderStatusChange to capture funds with Adyen - * @param captureRequest + * @param captureRequest with required information * @return `CommercePayments.GatewayResponse` */ public static CommercePayments.GatewayResponse capture(CommercePayments.CaptureRequest captureRequest) { @@ -42,7 +42,7 @@ public with sharing class AdyenCaptureHelper { currencyCode, captureRequest.amount, adyenAdapterMdt.Merchant_Account__c, - AdyenPaymentUtility.getReference(pa, captureRequest.amount), + AdyenPaymentUtility.getReference(captureRequest), adyenAdapterMdt.System_Integrator_Name__c ); diff --git a/force-app/main/default/classes/AdyenCaptureHelper.cls-meta.xml b/force-app/main/default/classes/AdyenCaptureHelper.cls-meta.xml index 754ecb1..f5e18fd 100644 --- a/force-app/main/default/classes/AdyenCaptureHelper.cls-meta.xml +++ b/force-app/main/default/classes/AdyenCaptureHelper.cls-meta.xml @@ -1,5 +1,5 @@ - 57.0 + 60.0 Active diff --git a/force-app/main/default/classes/AdyenPaymentHelper.cls-meta.xml b/force-app/main/default/classes/AdyenPaymentHelper.cls-meta.xml index 754ecb1..f5e18fd 100644 --- a/force-app/main/default/classes/AdyenPaymentHelper.cls-meta.xml +++ b/force-app/main/default/classes/AdyenPaymentHelper.cls-meta.xml @@ -1,5 +1,5 @@ - 57.0 + 60.0 Active diff --git a/force-app/main/default/classes/AdyenPaymentUtility.cls b/force-app/main/default/classes/AdyenPaymentUtility.cls index d492994..2bd8781 100644 --- a/force-app/main/default/classes/AdyenPaymentUtility.cls +++ b/force-app/main/default/classes/AdyenPaymentUtility.cls @@ -65,7 +65,7 @@ public with sharing class AdyenPaymentUtility { /** * Retrieves custom meta data associated with Adyen (Endpoint info) pulls all fields. - * + * @param metaType name of the custom metadata type with Adyen configuration * @return Adyen_Adapter__mdt for the passed metadata type with all fields. */ public static Adyen_Adapter__mdt retrieveGatewayMetadata(String metaType) { @@ -220,7 +220,6 @@ public with sharing class AdyenPaymentUtility { * * @param pa The PaymentAuthorization sObject * @return the GatewayRefNumber for the request. - * @see https://ca-test.adyen.com/ca/ca/accounts/showTx.shtml?pspReference=852588546520527A&txType=Payment */ public static String getCaptureGatewayRefNumber(PaymentAuthorization pa) { if (pa == null) { @@ -323,36 +322,20 @@ public with sharing class AdyenPaymentUtility { return String.valueOf(Math.round(Math.random() * MAX)).leftPad(stringLength, '0'); } - public static FulfillmentOrder getFulfillmentOrder(Id orderSummaryId, Decimal amount) { - try { - List fulfillmentOrders = [ - SELECT FulfillmentOrderNumber, Status, StatusCategory, Type, TypeCategory, GrandTotalAmount - FROM FulfillmentOrder - WHERE OrderSummaryId = :orderSummaryId - ORDER BY CreatedDate DESC - ]; - for (FulfillmentOrder fulfillmentOrder : fulfillmentOrders) { - if (fulfillmentOrder.GrandTotalAmount == amount) { - return fulfillmentOrder; - } - } - throw new AdyenAsyncAdapter.GatewayException( - 'Cannot find any fulfillment order related to order summary Id ' + orderSummaryId + ' with amount ' + amount - ); - } catch (Exception ex) { - logException(ex, LoggingLevel.ERROR); + public static String getReference(CommercePayments.CaptureRequest captureRequest) { + Id invoiceId = captureRequest.additionalData?.get('invoiceId'); + if (invoiceId == null) { + return getRandomNumber(16); } - return null; - } - - public static String getReference(SObject anyPaymentTypeRecord, Decimal amount) { - String randomNumber = getRandomNumber(16); - OrderPaymentSummary orderPaymentSummary = (OrderPaymentSummary)anyPaymentTypeRecord.getSObject('OrderPaymentSummary'); - if (orderPaymentSummary?.OrderSummaryId == null) { - return randomNumber; + List fulfillmentOrders = [ + SELECT FulfillmentOrderNumber + FROM FulfillmentOrder + WHERE InvoiceId = :invoiceId + ]; + if (fulfillmentOrders.isEmpty() || fulfillmentOrders.size() > 1) { + return getRandomNumber(16); } - String reference = getFulfillmentOrder(orderPaymentSummary.OrderSummaryId, amount)?.FulfillmentOrderNumber; - return String.isNotBlank(reference) ? reference : randomNumber; + return fulfillmentOrders[0].FulfillmentOrderNumber; } public static void logException(Exception ex, LoggingLevel loggingLevel) { diff --git a/force-app/main/default/classes/AdyenPaymentUtility.cls-meta.xml b/force-app/main/default/classes/AdyenPaymentUtility.cls-meta.xml index 754ecb1..f5e18fd 100644 --- a/force-app/main/default/classes/AdyenPaymentUtility.cls-meta.xml +++ b/force-app/main/default/classes/AdyenPaymentUtility.cls-meta.xml @@ -1,5 +1,5 @@ - 57.0 + 60.0 Active diff --git a/force-app/main/default/classes/AdyenPaymentUtilityTest.cls b/force-app/main/default/classes/AdyenPaymentUtilityTest.cls index 7688de1..d33a26e 100644 --- a/force-app/main/default/classes/AdyenPaymentUtilityTest.cls +++ b/force-app/main/default/classes/AdyenPaymentUtilityTest.cls @@ -1,6 +1,6 @@ @IsTest private class AdyenPaymentUtilityTest { - private static String ASSERT_PRICE_MESSAGE = 'For input price of '; + private static final String ASSERT_PRICE_MESSAGE = 'For input price of '; @IsTest static void createAuthorisationRequestTest() { // given @@ -128,6 +128,7 @@ private class AdyenPaymentUtilityTest { Assert.areEqual(productPrice*100, lineItems[0].amountExcludingTax); Assert.areEqual((productPrice+taxValue)*100, lineItems[0].amountIncludingTax); } + @IsTest(SeeAllData=true) // for ConnectApi use only static void addCreditMemoDataTest() { Integer productPrice = 100; @@ -161,65 +162,49 @@ private class AdyenPaymentUtilityTest { Assert.areEqual(0, lineItems[0].taxPercentage); } - @IsTest(SeeAllData=true) // for ConnectApi use only - static void getFulfillmentOrderTest() { - OrderPaymentSummary orderPaySum = createInvoiceAndRelatedRecords(100, 5); - Test.startTest(); - FulfillmentOrder fulfillmentOrder = AdyenPaymentUtility.getFulfillmentOrder(orderPaySum.OrderSummaryId, 105); - Test.stopTest(); - Assert.isNotNull(fulfillmentOrder); - } - - @IsTest - static void getFulfillmentOrderFailTest() { - Test.startTest(); - FulfillmentOrder fulfillmentOrder = AdyenPaymentUtility.getFulfillmentOrder(null, 0); - Test.stopTest(); - Assert.isNull(fulfillmentOrder); - } - @IsTest(SeeAllData=true) // for ConnectApi use only static void getReferenceTest() { + // given OrderPaymentSummary orderPaySum = createInvoiceAndRelatedRecords(100, 5); PaymentAuthorization payAuth = [ - SELECT OrderPaymentSummary.OrderSummaryId + SELECT Id FROM PaymentAuthorization WHERE OrderPaymentSummaryId = :orderPaySum.Id ]; - Payment payment = [ - SELECT OrderPaymentSummary.OrderSummaryId - FROM Payment - WHERE OrderPaymentSummaryId = :orderPaySum.Id - ]; FulfillmentOrder fulfillmentOrder = [ - SELECT FulfillmentOrderNumber + SELECT FulfillmentOrderNumber, InvoiceId, GrandTotalAmount FROM FulfillmentOrder WHERE OrderSummaryId = :orderPaySum.OrderSummaryId ]; - + CommercePayments.CaptureRequest captureRequest = new CommercePayments.CaptureRequest(Double.valueOf(fulfillmentOrder.GrandTotalAmount), payAuth.Id); + captureRequest.additionalData = new Map(); + captureRequest.additionalData.put('invoiceId', fulfillmentOrder.InvoiceId); + // when Test.startTest(); - String referencePayAuth = AdyenPaymentUtility.getReference(payAuth, 105); - String referencePayment = AdyenPaymentUtility.getReference(payment, 105); + String referencePayAuth = AdyenPaymentUtility.getReference(captureRequest); Test.stopTest(); - + // then Assert.areEqual(fulfillmentOrder.FulfillmentOrderNumber, referencePayAuth); - Assert.areEqual(fulfillmentOrder.FulfillmentOrderNumber, referencePayment); } @IsTest static void getReferenceRandomTest() { - OrderPaymentSummary orderPaymentSummary = new OrderPaymentSummary(Id = '0bM7Q000000QP0uUAG', OrderSummaryId = '1Os7Q000000Uh77SAC'); - PaymentAuthorization payAuth = new PaymentAuthorization(OrderPaymentSummary = orderPaymentSummary); - + // given + CommercePayments.CaptureRequest captureRequest1 = new CommercePayments.CaptureRequest(Double.valueOf(99.9), '0Xc7Q000000YFVcSAO'); + CommercePayments.CaptureRequest captureRequest2 = new CommercePayments.CaptureRequest(Double.valueOf(99.9), '0Xc7Q000000YFVcSAO'); + captureRequest2.additionalData = new Map(); + captureRequest2.additionalData.put('invoiceId', '3tt7Q000000cJbuQAE'); + // when Test.startTest(); - String reference = AdyenPaymentUtility.getReference(payAuth, 23); - String referenceNullOrderPaySum = AdyenPaymentUtility.getReference(new PaymentAuthorization(), 23); + String reference1 = AdyenPaymentUtility.getReference(captureRequest1); + String reference2 = AdyenPaymentUtility.getReference(captureRequest2); Test.stopTest(); - - Assert.isNotNull(reference); - Assert.isNotNull(referenceNullOrderPaySum); - Assert.areEqual(16, reference.length()); - Assert.areEqual(16, referenceNullOrderPaySum.length()); + // then + Assert.isNotNull(reference1); + Assert.isNotNull(reference2); + Assert.areEqual(16, reference1.length()); + Assert.areEqual(16, reference2.length()); + Assert.areNotEqual(reference1, reference2); } private static OrderPaymentSummary createInvoiceAndRelatedRecords(Decimal price, Decimal taxValue) { diff --git a/force-app/main/default/classes/AdyenPaymentUtilityTest.cls-meta.xml b/force-app/main/default/classes/AdyenPaymentUtilityTest.cls-meta.xml index 754ecb1..f5e18fd 100644 --- a/force-app/main/default/classes/AdyenPaymentUtilityTest.cls-meta.xml +++ b/force-app/main/default/classes/AdyenPaymentUtilityTest.cls-meta.xml @@ -1,5 +1,5 @@ - 57.0 + 60.0 Active From 7f9a87428a18a0b6883305840440f224210fc26c Mon Sep 17 00:00:00 2001 From: Danilo Cardoso Date: Fri, 14 Jun 2024 15:16:06 +0200 Subject: [PATCH 05/21] Log capture_failed notification web hook using standard payment gateway log record (#34) * feat: changing logic to be a bit more clear * feat: implementing capture-failed logic * fix: refactoring webhook response logic * fix: refactoring code with lint adjustments and removal of try catch with log * fix: removing System.debugs --------- Co-authored-by: daniloc --- .../AdyenAsyncAdapterTest.cls-meta.xml | 2 +- .../classes/AdyenAuthorisationHelper.cls | 2 - .../default/classes/AdyenCaptureHelper.cls | 1 - .../default/classes/AdyenPaymentHelper.cls | 163 +++++++----------- .../AdyenPaymentHelperTest.cls-meta.xml | 2 +- .../default/classes/AdyenPaymentUtility.cls | 126 ++++++-------- .../classes/AdyenPaymentUtilityTest.cls | 8 +- .../default/classes/AdyenRefundHelper.cls | 1 - .../main/default/classes/TestDataFactory.cls | 12 +- .../classes/TestDataFactory.cls-meta.xml | 2 +- 10 files changed, 132 insertions(+), 187 deletions(-) diff --git a/force-app/main/default/classes/AdyenAsyncAdapterTest.cls-meta.xml b/force-app/main/default/classes/AdyenAsyncAdapterTest.cls-meta.xml index d75b058..f5e18fd 100644 --- a/force-app/main/default/classes/AdyenAsyncAdapterTest.cls-meta.xml +++ b/force-app/main/default/classes/AdyenAsyncAdapterTest.cls-meta.xml @@ -1,5 +1,5 @@ - 51.0 + 60.0 Active diff --git a/force-app/main/default/classes/AdyenAuthorisationHelper.cls b/force-app/main/default/classes/AdyenAuthorisationHelper.cls index de04aae..48f4440 100644 --- a/force-app/main/default/classes/AdyenAuthorisationHelper.cls +++ b/force-app/main/default/classes/AdyenAuthorisationHelper.cls @@ -19,7 +19,6 @@ public with sharing class AdyenAuthorisationHelper { Map body = (Map)JSON.deserializeUntyped(response.getBody()); String resultCode = (String)body.get('resultCode'); if(resultCode != null) { - System.debug('-----> Adyen accepted request'); CommercePayments.AuthorizationResponse salesforceAuthResponse = new CommercePayments.AuthorizationResponse(); if(resultCode == 'Authorised') { Map additionalData = (Map)body.get('additionalData'); @@ -35,7 +34,6 @@ public with sharing class AdyenAuthorisationHelper { salesforceAuthResponse.setGatewayReferenceDetails(merchantAccountName); return salesforceAuthResponse; } else { - System.debug('-----> Adyen rejected request'); return new CommercePayments.GatewayErrorResponse( String.valueOf(response.getStatusCode()), String.valueOf(body.get('message')) diff --git a/force-app/main/default/classes/AdyenCaptureHelper.cls b/force-app/main/default/classes/AdyenCaptureHelper.cls index 65d0718..218ec0b 100644 --- a/force-app/main/default/classes/AdyenCaptureHelper.cls +++ b/force-app/main/default/classes/AdyenCaptureHelper.cls @@ -53,7 +53,6 @@ public with sharing class AdyenCaptureHelper { if (captureRequest.additionalData.containsKey('invoiceId')) { invoiceId = captureRequest.additionalData.get('invoiceId'); } - System.debug('--------> paymentRequest InvoiceId: ' + invoiceId); modRequest.setLineItems(AdyenPaymentUtility.addInvoiceData(invoiceId)); } diff --git a/force-app/main/default/classes/AdyenPaymentHelper.cls b/force-app/main/default/classes/AdyenPaymentHelper.cls index aa3e27d..8ea4df3 100644 --- a/force-app/main/default/classes/AdyenPaymentHelper.cls +++ b/force-app/main/default/classes/AdyenPaymentHelper.cls @@ -5,7 +5,7 @@ public with sharing class AdyenPaymentHelper { /** * Receives Payment Gateway Context from AdyenAsyncAdapter, looks at the context type and then invokes the appropriate Capture or Refund operation * - * @param paymentGatewayContext + * @param paymentGatewayContext from commerce api * @return `CommercePayments.GatewayResponse` */ public static CommercePayments.GatewayResponse handleFulfillmentOrderStatusChange(CommercePayments.PaymentGatewayContext paymentGatewayContext) { @@ -25,67 +25,43 @@ public with sharing class AdyenPaymentHelper { } public static CommercePayments.GatewayNotificationResponse handleAsyncNotificationCallback(CommercePayments.PaymentGatewayNotificationContext gatewayNotificationContext, String apexName) { - System.debug('----> Entering AdyenPaymentHelper.handleAsyncNotificationCallback PaymentGatewayNotificationContext= ' + gatewayNotificationContext); CommercePayments.PaymentGatewayNotificationRequest paymentGatewayNotificationRequest = Test.isRunningTest() ? null : gatewayNotificationContext.getPaymentGatewayNotificationRequest(); - CommercePayments.GatewayNotificationResponse gatewayNotificationResponse = new CommercePayments.GatewayNotificationResponse(); - - CommercePayments.NotificationSaveResult notificationSaveResult; NotificationRequestItem notificationRequestItem = parseAdyenNotificationRequest( paymentGatewayNotificationRequest ); - String adapterIdFromNotificationData; - if (notificationRequestItem.originalReference != null) { - adapterIdFromNotificationData = AdyenPaymentUtility.retrieveApexAdapterId(notificationRequestItem.originalReference); + + if (notificationRequestItem.originalReference == null) { + String msg = '[accepted] Notification skipped, original reference is not available'; + return createNotificationResponse(msg, AdyenConstants.HTTP_SUCCESS_CODE); } - + + String adapterIdFromNotificationData = AdyenPaymentUtility.retrieveApexAdapterId(notificationRequestItem.originalReference); Id adyenAdapterId = [SELECT Id FROM ApexClass WHERE Name = :apexName AND (NamespacePrefix = :AdyenOMSConstants.ADYEN_2GP_NAMESPACE OR NamespacePrefix = '') LIMIT 1].Id; - - if(adapterIdFromNotificationData == adyenAdapterId) { - - notificationSaveResult = createNotificationSaveResult( notificationRequestItem ); - + Boolean isAdapterSameAsThis = adapterIdFromNotificationData == adyenAdapterId; + if (isAdapterSameAsThis) { + CommercePayments.NotificationSaveResult notificationSaveResult = createNotificationSaveResult( notificationRequestItem ); if (notificationSaveResult != null) { - if(notificationSaveResult.isSuccess()){ // Notification is accepted by the platform - gatewayNotificationResponse.setStatusCode(AdyenConstants.HTTP_SUCCESS_CODE); - gatewayNotificationResponse.setResponseBody(Blob.valueOf(AdyenConstants.NOTIFICATION_ACCEPTED_RESPONSE )); - System.debug('----> Exiting AdyenPaymentHelper.handleAsyncNotificationCallback after the notification is accepted: ' + gatewayNotificationResponse); - return gatewayNotificationResponse; - } else { // Notification is not accepted by the platform, generate system event - gatewayNotificationResponse.setStatusCode(Integer.valueOf(AdyenConstants.HTTP_SERVER_ERROR_CODE)); - String msg = '[accepted] '; - if (notificationSaveResult != null && notificationSaveResult.getErrorMessage() != null) { - msg += notificationSaveResult.getErrorMessage(); - } - gatewayNotificationResponse.setResponseBody(Blob.valueOf(msg)); - return gatewayNotificationResponse; - } + return createNotificationResponse(AdyenConstants.NOTIFICATION_ACCEPTED_RESPONSE, AdyenConstants.HTTP_SUCCESS_CODE); } else { String msg = '[accepted] But unsupported notification type: ' + notificationRequestItem.eventCode; - gatewayNotificationResponse.setResponseBody(Blob.valueOf( msg )); - gatewayNotificationResponse.setStatusCode(AdyenConstants.HTTP_SUCCESS_CODE); - return gatewayNotificationResponse; + return createNotificationResponse(msg, AdyenConstants.HTTP_SUCCESS_CODE); } - } else { - String msg = '[accepted] '; - if (notificationRequestItem.originalReference == null) { - msg += 'Notification skipped, original reference is not available'; - gatewayNotificationResponse.setResponseBody(Blob.valueOf(msg)); - gatewayNotificationResponse.setStatusCode(AdyenConstants.HTTP_SUCCESS_CODE); - System.debug('----> Exiting AdyenPaymentHelper.handleAsyncNotificationCallback, originalReference is n/a: ' + gatewayNotificationResponse); - } else { - msg += 'But not processed - wrong payment adapter or wrong instance'; - gatewayNotificationResponse.setResponseBody(Blob.valueOf(msg)); - gatewayNotificationResponse.setStatusCode(AdyenConstants.HTTP_SUCCESS_CODE); - System.debug('----> Exiting AdyenPaymentHelper.handleAsyncNotificationCallback after identifying that it was the wrong payment adapter: ' + gatewayNotificationResponse); - } - return gatewayNotificationResponse; + String msg = '[accepted] But not processed - wrong payment adapter or wrong instance'; + return createNotificationResponse(msg, AdyenConstants.HTTP_SUCCESS_CODE); } } + + private static CommercePayments.GatewayNotificationResponse createNotificationResponse(String message, Integer httpCode) { + CommercePayments.GatewayNotificationResponse gatewayNotificationResponse = new CommercePayments.GatewayNotificationResponse(); + gatewayNotificationResponse.setResponseBody(Blob.valueOf(message)); + gatewayNotificationResponse.setStatusCode(httpCode); + return gatewayNotificationResponse; + } /** * Take the http request from the async notification callback and deserializes it into AdyenNotificationResponse. * - * @param notificationRequest The body of the Adyen notification request. - * @return AdyenNotificationRequest The deserialized version of the Adyen nodification request. + * @param notificationRequest The body of the Adyen notification request. + * @return AdyenNotificationRequest The deserialized version of the Adyen notification request. * * @see https://docs.adyen.com/development-resources/webhooks/understand-notifications */ @@ -93,9 +69,9 @@ public with sharing class AdyenPaymentHelper { String adyenNotificationRequestPayload = Test.isRunningTest() ? TEST_NOTIFICATION_REQUEST_BODY : AdyenPaymentUtility.makeSalesforceCompatible(notificationRequest.getRequestBody().toString()); AdyenNotification adyenNotification = (AdyenNotification) JSON.deserialize(adyenNotificationRequestPayload, AdyenNotification.class); NotificationRequestItem notificationRequestItem = new NotificationRequestItem(); - if(adyenNotification != null) { - for(NotificationItems notificationItem : adyenNotification.notificationItems) { - if(notificationItem.NotificationRequestItem != null) { + if (adyenNotification != null) { + for (NotificationItems notificationItem : adyenNotification.notificationItems) { + if (notificationItem.NotificationRequestItem != null) { notificationRequestItem = notificationItem.NotificationRequestItem; } } @@ -105,66 +81,53 @@ public with sharing class AdyenPaymentHelper { /** - * Creates and records (ie saves) the notificationsaveresult. + * Creates and records (ie saves) the notification save result. * - * @param notificationRequestItem + * @param notificationRequestItem parsed from Adyen web hook * @return CommercePayments.NotificationSaveResult. */ @TestVisible private static CommercePayments.NotificationSaveResult createNotificationSaveResult( NotificationRequestItem notificationRequestItem ) { - Boolean unsupportedNotificationType = false; - CommercePayments.BaseNotification notification = null; - CommercePayments.NotificationStatus notificationStatus = null; - CommercePayments.SalesforceResultCode notificationStatusSF = null; - - if (!Boolean.valueOf(notificationRequestItem.success)){ - notificationStatus = CommercePayments.NotificationStatus.Failed; - notificationStatusSF = CommercePayments.SalesforceResultCode.Decline; - } else { - notificationStatus = CommercePayments.NotificationStatus.Success; - notificationStatusSF = CommercePayments.SalesforceResultCode.Success; - } - - if (AdyenConstants.NOTIFICATION_REQUEST_TYPE_CAPTURE.equalsIgnoreCase(notificationRequestItem.eventCode)) { + CommercePayments.BaseNotification notification; + CommercePayments.NotificationStatus notificationStatus; + CommercePayments.SalesforceResultCode notificationStatusSF; + + String gatewayMessageTemplate; + Boolean isCaptureRequest = AdyenConstants.NOTIFICATION_REQUEST_TYPE_CAPTURE.equalsIgnoreCase(notificationRequestItem.eventCode); + Boolean isRefundRequest = AdyenConstants.NOTIFICATION_REQUEST_TYPE_REFUND.equalsIgnoreCase(notificationRequestItem.eventCode); + Boolean isCaptureFailedRequest = AdyenConstants.NOTIFICATION_REQUEST_TYPE_CAPTURE_FAILED.endsWithIgnoreCase(notificationRequestItem.eventCode); + if (isCaptureRequest || isCaptureFailedRequest) { notification = new CommercePayments.CaptureNotification(); - } else if (AdyenConstants.NOTIFICATION_REQUEST_TYPE_REFUND.equalsIgnoreCase(notificationRequestItem.eventCode)) { + gatewayMessageTemplate = '[capture-{0}] {1}'; + } else if (isRefundRequest) { notification = new CommercePayments.ReferencedRefundNotification(); + gatewayMessageTemplate = '[refund-{0}] {1}'; } else { - unsupportedNotificationType = true; + return null; // unsupported notification request } - - CommercePayments.NotificationSaveResult notificationSaveResult; - if(!unsupportedNotificationType) { - notification.setStatus(notificationStatus); - notification.setSalesforceResultCodeInfo(new CommercePayments.SalesforceResultCodeInfo(notificationStatusSF)); - notification.setGatewayReferenceNumber(notificationRequestItem.pspReference); - notification.setGatewayResultCode(notificationRequestItem.eventCode); - notification.setGatewayResultCodeDescription(notificationRequestItem.reason); - if(String.isBlank(notificationRequestItem.reason) && notificationStatus == CommercePayments.NotificationStatus.Success) { - if (AdyenConstants.NOTIFICATION_REQUEST_TYPE_CAPTURE.equalsIgnoreCase(notificationRequestItem.eventCode)) { - notification.setGatewayMessage('[capture-complete]'); - } else if (AdyenConstants.NOTIFICATION_REQUEST_TYPE_REFUND.equalsIgnoreCase(notificationRequestItem.eventCode)) { - notification.setGatewayMessage('[refund-complete]'); - } - } else{ - notification.setGatewayMessage(notificationRequestItem.reason); - } - - Decimal value; - if(notificationRequestItem.amount != null) { - value = notificationRequestItem.amount.value; - } - value = value / AdyenPaymentUtility.getAmountMultiplier(notificationRequestItem.amount.currency_x); - - Double dValue = value; - notification.setAmount(dValue); - notificationSaveResult = CommercePayments.NotificationClient.record(notification); - System.debug(JSON.serialize(notificationSaveResult)); - return notificationSaveResult; + + String result; + Boolean isSuccessfulNotification = Boolean.valueOf(notificationRequestItem.success); + if (isSuccessfulNotification && !isCaptureFailedRequest) { + notificationStatus = CommercePayments.NotificationStatus.Success; + notificationStatusSF = CommercePayments.SalesforceResultCode.Success; + result = 'complete'; } else { - notificationSaveResult = null; - System.debug('---> Exiting AdyenPaymentHelper.createNotificationSaveResult after ignoring unsupported notification= ' + notificationRequestItem.eventCode); - return notificationSaveResult; + notificationStatus = CommercePayments.NotificationStatus.Failed; + notificationStatusSF = CommercePayments.SalesforceResultCode.Decline; + result = 'fail'; } + String gatewayMessage = String.format(gatewayMessageTemplate, new List{result, notificationRequestItem.reason}); + Decimal priceMinorUnits = notificationRequestItem.amount != null ? notificationRequestItem.amount.value : 0; + Decimal price = priceMinorUnits / AdyenPaymentUtility.getAmountMultiplier(notificationRequestItem.amount.currency_x); + + notification.setGatewayMessage(gatewayMessage); + notification.setStatus(notificationStatus); + notification.setSalesforceResultCodeInfo(new CommercePayments.SalesforceResultCodeInfo(notificationStatusSF)); + notification.setGatewayReferenceNumber(notificationRequestItem.pspReference); + notification.setGatewayResultCode(notificationRequestItem.eventCode); + notification.setAmount(Double.valueOf(price)); + + return CommercePayments.NotificationClient.record(notification); } } \ No newline at end of file diff --git a/force-app/main/default/classes/AdyenPaymentHelperTest.cls-meta.xml b/force-app/main/default/classes/AdyenPaymentHelperTest.cls-meta.xml index 45cccbd..3a10d2e 100644 --- a/force-app/main/default/classes/AdyenPaymentHelperTest.cls-meta.xml +++ b/force-app/main/default/classes/AdyenPaymentHelperTest.cls-meta.xml @@ -1,5 +1,5 @@ - 57.0 + 60.0 Active \ No newline at end of file diff --git a/force-app/main/default/classes/AdyenPaymentUtility.cls b/force-app/main/default/classes/AdyenPaymentUtility.cls index 2bd8781..f448025 100644 --- a/force-app/main/default/classes/AdyenPaymentUtility.cls +++ b/force-app/main/default/classes/AdyenPaymentUtility.cls @@ -143,9 +143,8 @@ public with sharing class AdyenPaymentUtility { ORDER BY CreatedDate DESC ]; - if(!paymentAuthorizations.isEmpty()) { + if (!paymentAuthorizations.isEmpty()) { pa = paymentAuthorizations[0]; - System.debug(LoggingLevel.INFO, 'SalesChannel Config: ' + pa.OrderPaymentSummary.OrderSummary.SalesChannel.AdyenMerchantID__c); } } return pa; @@ -171,8 +170,8 @@ public with sharing class AdyenPaymentUtility { /** * Retrieve apex adapter id from the gateway reference number. * - * @param gatewayRefNumber original payment gatewayrefnumber as recieved in the notification - * @return apexclass id for the payment gateway adapter. + * @param gatewayRefNumber original payment gateway reference number as received in the notification + * @return apex class id for the payment gateway adapter. */ public static String retrieveApexAdapterId(String gatewayRefNumber) { String apexAdapterId = null; @@ -338,13 +337,6 @@ public with sharing class AdyenPaymentUtility { return fulfillmentOrders[0].FulfillmentOrderNumber; } - public static void logException(Exception ex, LoggingLevel loggingLevel) { - System.debug(loggingLevel, 'Exception message : ' + ex.getMessage()); - System.debug(loggingLevel, 'Exception type : ' + ex.getTypeName()); - System.debug(loggingLevel, 'Exception line : ' + ex.getLineNumber()); - System.debug(loggingLevel, 'Stacktrace : ' + ex.getStackTraceString()); - } - /** * Round an amount to a normalized value for consistency * @@ -361,7 +353,6 @@ public with sharing class AdyenPaymentUtility { * @return application information map for the request. */ public static ApplicationInfo getApplicationInfo(String integratorName) { - ApplicationInfo info = new ApplicationInfo(); ExternalPlatform exPlatform = new ExternalPlatform(); @@ -423,61 +414,56 @@ public with sharing class AdyenPaymentUtility { * * @return AuthorisationRequest to send to Adyen. */ - public static AuthorisationRequest createAuthorisationRequest(CommercePayments.AuthorizationRequest authRequest, Adyen_Adapter__mdt adyenAdapterMdt){ - + public static AuthorisationRequest createAuthorisationRequest(CommercePayments.AuthorizationRequest authRequest, Adyen_Adapter__mdt adyenAdapterMdt) { AuthorisationRequest adyenAuthorisationRequest = new AuthorisationRequest(); - try{ - CommercePayments.AuthApiPaymentMethodRequest paymentMethod = authRequest.paymentMethod; - String currencyCode = authRequest.currencyIsoCode.toUpperCase(); - - Decimal authAmount = authRequest.amount; - adyenAuthorisationRequest.amount = new Amount(); - adyenAuthorisationRequest.amount.currency_x = currencyCode; - adyenAuthorisationRequest.amount.value = (authAmount * AdyenPaymentUtility.getAmountMultiplier(currencyCode)).round(System.RoundingMode.HALF_UP); + CommercePayments.AuthApiPaymentMethodRequest paymentMethod = authRequest.paymentMethod; + String currencyCode = authRequest.currencyIsoCode.toUpperCase(); - //Use existing token to create auth request - if(paymentMethod.id != null){ - //paymentMethod.id would be a string that represents the Salesforce record id of CardPaymentMethod or AlternativePaymentMethod object - String adyenToken; - Id recordId = paymentMethod.id; - String sObjName = recordId.getSobjectType().getDescribe().getName(); //determine object name - - if(sObjName == AdyenOMSConstants.CARD_PAYMENTMETHOD_OBJECT) { - //for CardPaymentMethod : Use GatewayTokenEncrypted field to retrieve token - CardPaymentMethod cpmRecord = [SELECT Id, GatewayTokenEncrypted FROM CardPaymentMethod WHERE Id = :recordId LIMIT 1]; - adyenToken = cpmRecord.GatewayTokenEncrypted; - } else if(sObjName == AdyenOMSConstants.ALTERNATIVE_PAYMENTMETHOD_OBJECT) { - //for AlternativePaymentMethod : Use GatewayToken field to retrieve token - AlternativePaymentMethod apmRecord = [SELECT Id, GatewayToken FROM AlternativePaymentMethod WHERE Id = :recordId LIMIT 1]; - adyenToken = apmRecord.GatewayToken; - } - - CardDetails cardDetails = new CardDetails(); - cardDetails.storedPaymentMethodId = adyenToken; - adyenAuthorisationRequest.paymentMethod = cardDetails; - adyenAuthorisationRequest.shopperInteraction = AuthorisationRequest.ShopperInteractionEnum.ContAuth; - adyenAuthorisationRequest.recurringProcessingModel = AuthorisationRequest.RecurringProcessingModelEnum.CardOnFile; - - } else if(paymentMethod.cardPaymentMethod != null) { - //use new card details to create auth request - CommercePayments.CardPaymentMethodRequest cpmRequest = paymentMethod.cardPaymentMethod; - CardDetails cardDetails = new CardDetails(); - cardDetails.number_x = cpmRequest.cardNumber; - cardDetails.expiryMonth = String.valueOf(cpmRequest.expiryMonth); - cardDetails.expiryYear = String.valueOf(cpmRequest.expiryYear); - cardDetails.holderName = cpmRequest.cardHolderName; - cardDetails.cvc = cpmRequest.cvv; - adyenAuthorisationRequest.paymentMethod = cardDetails; - adyenAuthorisationRequest.shopperInteraction = AuthorisationRequest.ShopperInteractionEnum.Ecommerce; + Decimal authAmount = authRequest.amount; + adyenAuthorisationRequest.amount = new Amount(); + adyenAuthorisationRequest.amount.currency_x = currencyCode; + adyenAuthorisationRequest.amount.value = (authAmount * AdyenPaymentUtility.getAmountMultiplier(currencyCode)).round(System.RoundingMode.HALF_UP); + + //Use existing token to create auth request + if (paymentMethod.id != null) { + //paymentMethod.id would be a string that represents the Salesforce record id of CardPaymentMethod or AlternativePaymentMethod object + String adyenToken; + Id recordId = paymentMethod.id; + String sObjName = recordId.getSobjectType().getDescribe().getName(); //determine object name + + if (sObjName == AdyenOMSConstants.CARD_PAYMENTMETHOD_OBJECT) { + //for CardPaymentMethod : Use GatewayTokenEncrypted field to retrieve token + CardPaymentMethod cpmRecord = [SELECT Id, GatewayTokenEncrypted FROM CardPaymentMethod WHERE Id = :recordId LIMIT 1]; + adyenToken = cpmRecord.GatewayTokenEncrypted; + } else if (sObjName == AdyenOMSConstants.ALTERNATIVE_PAYMENTMETHOD_OBJECT) { + //for AlternativePaymentMethod : Use GatewayToken field to retrieve token + AlternativePaymentMethod apmRecord = [SELECT Id, GatewayToken FROM AlternativePaymentMethod WHERE Id = :recordId LIMIT 1]; + adyenToken = apmRecord.GatewayToken; } - - adyenAuthorisationRequest.reference = AdyenPaymentUtility.getRandomNumber(16); - adyenAuthorisationRequest.merchantAccount = adyenAdapterMdt.Merchant_Account__c; - adyenAuthorisationRequest.shopperReference = UserInfo.getUserId(); - adyenAuthorisationRequest.applicationInfo = AdyenPaymentUtility.getApplicationInfo(adyenAdapterMdt.System_Integrator_Name__c); - } catch(Exception ex) { - logException(ex, LoggingLevel.ERROR); + + CardDetails cardDetails = new CardDetails(); + cardDetails.storedPaymentMethodId = adyenToken; + adyenAuthorisationRequest.paymentMethod = cardDetails; + adyenAuthorisationRequest.shopperInteraction = AuthorisationRequest.ShopperInteractionEnum.ContAuth; + adyenAuthorisationRequest.recurringProcessingModel = AuthorisationRequest.RecurringProcessingModelEnum.CardOnFile; + + } else if (paymentMethod.cardPaymentMethod != null) { + //use new card details to create auth request + CommercePayments.CardPaymentMethodRequest cpmRequest = paymentMethod.cardPaymentMethod; + CardDetails cardDetails = new CardDetails(); + cardDetails.number_x = cpmRequest.cardNumber; + cardDetails.expiryMonth = String.valueOf(cpmRequest.expiryMonth); + cardDetails.expiryYear = String.valueOf(cpmRequest.expiryYear); + cardDetails.holderName = cpmRequest.cardHolderName; + cardDetails.cvc = cpmRequest.cvv; + adyenAuthorisationRequest.paymentMethod = cardDetails; + adyenAuthorisationRequest.shopperInteraction = AuthorisationRequest.ShopperInteractionEnum.Ecommerce; } + + adyenAuthorisationRequest.reference = AdyenPaymentUtility.getRandomNumber(16); + adyenAuthorisationRequest.merchantAccount = adyenAdapterMdt.Merchant_Account__c; + adyenAuthorisationRequest.shopperReference = UserInfo.getUserId(); + adyenAuthorisationRequest.applicationInfo = AdyenPaymentUtility.getApplicationInfo(adyenAdapterMdt.System_Integrator_Name__c); return adyenAuthorisationRequest; } @@ -489,15 +475,11 @@ public with sharing class AdyenPaymentUtility { public static HttpResponse sendAuthorisationRequest(AuthorisationRequest authRequest, Adyen_Adapter__mdt adyenAdapterMdt){ HttpResponse response; String endpoint; - try{ - String body = AdyenPaymentUtility.makeAdyenCompatible(JSON.serialize(authRequest, true)); - String apiKey = MERCHANT_API_KEY; - endpoint = Test.isRunningTest() ? TEST_ENDPOINT + adyenAdapterMdt.Authorize_Endpoint__c : adyenAdapterMdt.Endpoint_Path__c + adyenAdapterMdt.Endpoint_Api_Version__c + adyenAdapterMdt.Authorize_Endpoint__c; - AdyenClient client = new AdyenClient(apiKey, endpoint); - response = client.request(client.config, body); - } catch(Exception ex) { - logException(ex, LoggingLevel.ERROR); - } + String body = AdyenPaymentUtility.makeAdyenCompatible(JSON.serialize(authRequest, true)); + String apiKey = MERCHANT_API_KEY; + endpoint = Test.isRunningTest() ? TEST_ENDPOINT + adyenAdapterMdt.Authorize_Endpoint__c : adyenAdapterMdt.Endpoint_Path__c + adyenAdapterMdt.Endpoint_Api_Version__c + adyenAdapterMdt.Authorize_Endpoint__c; + AdyenClient client = new AdyenClient(apiKey, endpoint); + response = client.request(client.config, body); return response; } diff --git a/force-app/main/default/classes/AdyenPaymentUtilityTest.cls b/force-app/main/default/classes/AdyenPaymentUtilityTest.cls index d33a26e..4bbe800 100644 --- a/force-app/main/default/classes/AdyenPaymentUtilityTest.cls +++ b/force-app/main/default/classes/AdyenPaymentUtilityTest.cls @@ -11,7 +11,7 @@ private class AdyenPaymentUtilityTest { Long expectedValue; for (Integer i = 0; i < 10; i++) { price = 1008.90 + (0.01 * i); - authRequest = new CommercePayments.AuthorizationRequest(price); + authRequest = TestDataFactory.createAuthorisationRequest(null, price); authRequest.currencyIsoCode = 'USD'; // when adyenAuthRequest = AdyenPaymentUtility.createAuthorisationRequest(authRequest, adyenAdapterMdt); @@ -22,7 +22,7 @@ private class AdyenPaymentUtilityTest { // given a higher amount of decimals for (Integer i = 0; i < 10; i++) { price = 1008.900 + (i * 0.001); - authRequest = new CommercePayments.AuthorizationRequest(price); + authRequest = TestDataFactory.createAuthorisationRequest(null, price); authRequest.currencyIsoCode = 'USD'; // when adyenAuthRequest = AdyenPaymentUtility.createAuthorisationRequest(authRequest, adyenAdapterMdt); @@ -33,7 +33,7 @@ private class AdyenPaymentUtilityTest { // given 0 decimals currency for (Integer i = 0; i < 10; i++) { price = 100890 + i; - authRequest = new CommercePayments.AuthorizationRequest(price); + authRequest = TestDataFactory.createAuthorisationRequest(null, price); authRequest.currencyIsoCode = 'JPY'; // when adyenAuthRequest = AdyenPaymentUtility.createAuthorisationRequest(authRequest, adyenAdapterMdt); @@ -44,7 +44,7 @@ private class AdyenPaymentUtilityTest { // given 3 decimals currency for (Integer i = 0; i < 10; i++) { price = 100.890 + (i * 0.001); - authRequest = new CommercePayments.AuthorizationRequest(price); + authRequest = TestDataFactory.createAuthorisationRequest(null, price); authRequest.currencyIsoCode = 'JOD'; // when adyenAuthRequest = AdyenPaymentUtility.createAuthorisationRequest(authRequest, adyenAdapterMdt); diff --git a/force-app/main/default/classes/AdyenRefundHelper.cls b/force-app/main/default/classes/AdyenRefundHelper.cls index 7ded013..a860391 100644 --- a/force-app/main/default/classes/AdyenRefundHelper.cls +++ b/force-app/main/default/classes/AdyenRefundHelper.cls @@ -50,7 +50,6 @@ public with sharing class AdyenRefundHelper { if (String.isNotBlank(payment.PaymentAuthorization.Adyen_Payment_Method_Variant__c)) { if (payment.PaymentAuthorization.Adyen_Payment_Method_Variant__c.equalsIgnoreCase('Paypal') && String.isNotBlank(payment.GatewayRefDetails)) { String refundReference = modRequest.getReference() + payment.GatewayRefDetails; //payment.GatewayRefDetails has the capture reference - System.debug('refundReference for Paypal :: ' + refundReference); modRequest.setReference(refundReference); } } diff --git a/force-app/main/default/classes/TestDataFactory.cls b/force-app/main/default/classes/TestDataFactory.cls index e4ade74..e12ca4f 100644 --- a/force-app/main/default/classes/TestDataFactory.cls +++ b/force-app/main/default/classes/TestDataFactory.cls @@ -107,15 +107,19 @@ public class TestDataFactory { } public static CommercePayments.AuthorizationRequest createAuthorisationRequest(Id payMethodId) { - CommercePayments.AuthorizationRequest authRequest = new CommercePayments.AuthorizationRequest(TEST_AMOUNT); + return createAuthorisationRequest(payMethodId, TEST_AMOUNT); + } + + public static CommercePayments.AuthorizationRequest createAuthorisationRequest(Id payMethodId, Double price) { + CommercePayments.AuthorizationRequest authRequest = new CommercePayments.AuthorizationRequest(price); authRequest.currencyIsoCode = ACTIVE_CURRENCY; - + CommercePayments.CardPaymentMethodRequest cardPayMeth = new CommercePayments.CardPaymentMethodRequest(CommercePayments.CardCategory.CreditCard); authRequest.paymentMethod = new CommercePayments.AuthApiPaymentMethodRequest(cardPayMeth); authRequest.paymentMethod.id = payMethodId; - CommercePayments.PaymentGatewayContext payGatecontext = new CommercePayments.PaymentGatewayContext(authRequest, CommercePayments.RequestType.Authorize); - CommercePayments.PaymentGatewayRequest paymentRequest = payGatecontext.getPaymentRequest(); + CommercePayments.PaymentGatewayContext payGateContext = new CommercePayments.PaymentGatewayContext(authRequest, CommercePayments.RequestType.Authorize); + CommercePayments.PaymentGatewayRequest paymentRequest = payGateContext.getPaymentRequest(); return (CommercePayments.AuthorizationRequest)paymentRequest; } diff --git a/force-app/main/default/classes/TestDataFactory.cls-meta.xml b/force-app/main/default/classes/TestDataFactory.cls-meta.xml index 9bbf7b4..3a10d2e 100644 --- a/force-app/main/default/classes/TestDataFactory.cls-meta.xml +++ b/force-app/main/default/classes/TestDataFactory.cls-meta.xml @@ -1,5 +1,5 @@ - 56.0 + 60.0 Active \ No newline at end of file From cbead9f8b43c54818560013f89fe878346885ecb Mon Sep 17 00:00:00 2001 From: Danilo Cardoso Date: Tue, 18 Jun 2024 16:23:58 +0200 Subject: [PATCH 06/21] fix: removing deprecated paymentPspReference attribute (#36) Co-authored-by: daniloc --- force-app/main/default/classes/TestDataFactory.cls | 1 - 1 file changed, 1 deletion(-) diff --git a/force-app/main/default/classes/TestDataFactory.cls b/force-app/main/default/classes/TestDataFactory.cls index e12ca4f..c9a58e6 100644 --- a/force-app/main/default/classes/TestDataFactory.cls +++ b/force-app/main/default/classes/TestDataFactory.cls @@ -99,7 +99,6 @@ public class TestDataFactory { nri.eventCode = eventCode; nri.pspReference = GATEWAY_REF; nri.eventDate = String.valueOf(System.today()); - nri.paymentpspReference = 'paymentpspref'; nri.success = 'true'; nri.merchantAccountCode = 'merchant_account_code'; nri.originalReference = originalRef; From 91033a9a4d3b75c85bbb9bf6668c8ac6948365f8 Mon Sep 17 00:00:00 2001 From: Danilo Cardoso Date: Mon, 24 Jun 2024 10:29:07 +0200 Subject: [PATCH 07/21] Remove hardcoded namespace in the code (#35) * feat: changing logic to be a bit more clear * feat: implementing capture-failed logic * fix: refactoring webhook response logic * fix: refactoring code with lint adjustments and removal of try catch with log * fix: removing System.debugs * feat: refactoring code and adding namespace field * fix: lint issue * fix: unit tests and linting * fix: more lint issues * fix: using namespace in our scratch org to run our tests * fix: grammar issue --------- Co-authored-by: daniloc --- .github/workflows/unit-tests.yml | 2 +- .../default/classes/AdyenAsyncAdapter.cls | 11 +- .../default/classes/AdyenAsyncAdapterTest.cls | 22 +-- .../AdyenAuthorisationHelper.cls-meta.xml | 2 +- .../default/classes/AdyenCaptureHelper.cls | 18 +- .../classes/AdyenOMSConstants.cls-meta.xml | 2 +- .../default/classes/AdyenPaymentHelper.cls | 13 +- .../default/classes/AdyenPaymentUtility.cls | 178 +++++++----------- .../classes/AdyenPaymentUtilityTest.cls | 23 +-- .../default/classes/AdyenRefundHelper.cls | 15 +- .../classes/AdyenRefundHelper.cls-meta.xml | 2 +- .../main/default/classes/TestDataFactory.cls | 51 +++-- .../Adyen_Adapter.AdyenDefault.md-meta.xml | 12 +- ...__mdt-Adyen Adapter Layout.layout-meta.xml | 32 ++-- .../Authorize_Endpoint__c.field-meta.xml | 5 +- .../fields/Capture_Endpoint__c.field-meta.xml | 3 +- .../Endpoint_Api_Version__c.field-meta.xml | 7 +- .../fields/Endpoint_Method__c.field-meta.xml | 15 -- .../fields/Endpoint_Path__c.field-meta.xml | 3 +- .../fields/Merchant_Account__c.field-meta.xml | 1 - .../Package_Namespace__c.field-meta.xml | 13 ++ .../fields/Refund_Endpoint__c.field-meta.xml | 3 +- .../Single_Currency_Code__c.field-meta.xml | 14 -- .../System_Integrator_Name__c.field-meta.xml | 1 - sfdx-project.json | 2 +- 25 files changed, 178 insertions(+), 272 deletions(-) delete mode 100644 force-app/main/default/objects/Adyen_Adapter__mdt/fields/Endpoint_Method__c.field-meta.xml create mode 100644 force-app/main/default/objects/Adyen_Adapter__mdt/fields/Package_Namespace__c.field-meta.xml delete mode 100644 force-app/main/default/objects/Adyen_Adapter__mdt/fields/Single_Currency_Code__c.field-meta.xml diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index aee953f..204de45 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -32,7 +32,7 @@ jobs: run: sf org login sfdx-url -f secret.json --set-default-dev-hub - name: Create Scratch Org - run: sf org create scratch -f config/project-scratch-def.json --set-default --alias ScratchOrg --no-namespace -y 1 + run: sf org create scratch -f config/project-scratch-def.json --set-default --alias ScratchOrg -y 1 - name: Checkout Apex-Library Repository uses: actions/checkout@v4 diff --git a/force-app/main/default/classes/AdyenAsyncAdapter.cls b/force-app/main/default/classes/AdyenAsyncAdapter.cls index 1a3952c..4f94377 100644 --- a/force-app/main/default/classes/AdyenAsyncAdapter.cls +++ b/force-app/main/default/classes/AdyenAsyncAdapter.cls @@ -6,7 +6,6 @@ * * @see AdyenPaymentHelper * @see AdyenClient - * @see https://quip.com/z6RVAJzUKYaf */ global with sharing class AdyenAsyncAdapter implements CommercePayments.PaymentGatewayAdapter, CommercePayments.PaymentGatewayAsyncAdapter { @@ -18,8 +17,8 @@ global with sharing class AdyenAsyncAdapter implements CommercePayments.Paymen * The entry point for processing payment requests. Returns the response from the payment gateway. * Accepts the gateway context request and handover the operation to AdyenPaymentHelper to call the appropriate capture or refund operation. * - * @param paymentGatewayContext - * @return CommercePayments.GatewayResponse + * @param paymentGatewayContext from SF Commerce Payments + * @return CommercePayments.GatewayResponse * * @implNotes * [CAPTURE] is called after setting Fulfillment.Status to 'Fulfilled' which in turns fires processes @@ -28,17 +27,17 @@ global with sharing class AdyenAsyncAdapter implements CommercePayments.Paymen * [REFUND] is called by using the action on the order summary page (left hand side). * */ - global CommercePayments.GatewayResponse processRequest(CommercePayments.paymentGatewayContext paymentGatewayContext) { + global CommercePayments.GatewayResponse processRequest(CommercePayments.PaymentGatewayContext paymentGatewayContext) { return AdyenPaymentHelper.handleFulfillmentOrderStatusChange(paymentGatewayContext); } /** * Listens to the incoming async notification callback from Adyen and handover to AdyenPaymentHelper for processing * - * @param gatewayNotificationContext + * @param gatewayNotificationContext from SF Commerce Payments * @return CommercePayments.GatewayNotificationResponse */ - public CommercePayments.GatewayNotificationResponse processNotification(CommercePayments.PaymentGatewayNotificationContext gatewayNotificationContext) { + global CommercePayments.GatewayNotificationResponse processNotification(CommercePayments.PaymentGatewayNotificationContext gatewayNotificationContext) { String apexName = String.valueOf(this).substring(0, String.valueOf(this).indexOf(':')); return AdyenPaymentHelper.handleAsyncNotificationCallback(gatewayNotificationContext, apexName); } diff --git a/force-app/main/default/classes/AdyenAsyncAdapterTest.cls b/force-app/main/default/classes/AdyenAsyncAdapterTest.cls index 0b01c0b..e324a42 100644 --- a/force-app/main/default/classes/AdyenAsyncAdapterTest.cls +++ b/force-app/main/default/classes/AdyenAsyncAdapterTest.cls @@ -1,4 +1,4 @@ -@isTest +@IsTest private class AdyenAsyncAdapterTest { @TestSetup static void makeData() { @@ -24,7 +24,7 @@ private class AdyenAsyncAdapterTest { @IsTest static void testCaptureOutboundFailure() { - Test.setMock(HttpCalloutMock.class, new TestDataFactory.EchoHttpMock()); + Test.setMock(HttpCalloutMock.class, new TestDataFactory.FailureResponse()); Test.startTest(); AdyenPaymentUtility.skipMerchantAccount = true; @@ -44,11 +44,11 @@ private class AdyenAsyncAdapterTest { Test.startTest(); CommercePayments.CaptureRequest captureRequest = new CommercePayments.CaptureRequest(TestDataFactory.TEST_AMOUNT, null); CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(captureRequest, CommercePayments.RequestType.Capture); - CommercePayments.GatewayResponse captureResponse = (CommercePayments.GatewayResponse) TestDataFactory.adyenAdapter.processRequest(context); + TestDataFactory.adyenAdapter.processRequest(context); Test.stopTest(); Assert.fail('Exception expected'); } catch(Exception ex) { - Assert.areEqual('Payment Authorization Missing', ex.getMessage(), 'Payment Authorization is available'); + Assert.isInstanceOfType(ex, System.QueryException.class, 'Payment Authorization is available'); } } @@ -60,7 +60,7 @@ private class AdyenAsyncAdapterTest { Test.startTest(); CommercePayments.CaptureRequest captureRequest = new CommercePayments.CaptureRequest(null, authId); CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(captureRequest, CommercePayments.RequestType.Capture); - CommercePayments.GatewayResponse captureResponse = (CommercePayments.GatewayResponse) TestDataFactory.adyenAdapter.processRequest(context); + TestDataFactory.adyenAdapter.processRequest(context); Test.stopTest(); Assert.fail('Exception expected'); } catch(Exception ex) { @@ -101,12 +101,12 @@ private class AdyenAsyncAdapterTest { CommercePayments.GatewayResponse refundResponse = (CommercePayments.GatewayResponse) TestDataFactory.adyenAdapter.processRequest(context); Test.stopTest(); - Assert.isTrue(refundResponse.toString().contains(TestDataFactory.TEST_SHOPPER_REFERENCE)); + Assert.isTrue(refundResponse.toString().contains('received')); } @IsTest static void testRefundOutboundFailure() { - Test.setMock(HttpCalloutMock.class, new TestDataFactory.EchoHttpMock()); + Test.setMock(HttpCalloutMock.class, new TestDataFactory.FailureResponse()); Test.startTest(); AdyenPaymentUtility.skipMerchantAccount = true; @@ -126,11 +126,11 @@ private class AdyenAsyncAdapterTest { Test.startTest(); CommercePayments.ReferencedRefundRequest refundRequest = new CommercePayments.ReferencedRefundRequest(TestDataFactory.TEST_AMOUNT, null); CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(refundRequest, CommercePayments.RequestType.ReferencedRefund); - CommercePayments.GatewayResponse refundResponse = (CommercePayments.GatewayResponse) TestDataFactory.adyenAdapter.processRequest(context); + TestDataFactory.adyenAdapter.processRequest(context); Test.stopTest(); Assert.fail('Exception expected'); } catch(Exception ex) { - Assert.areEqual('Payment Info Missing', ex.getMessage(), 'Payment Info is available.'); + Assert.isInstanceOfType(ex, System.QueryException.class, 'Payment Authorization is available'); } } @@ -142,10 +142,10 @@ private class AdyenAsyncAdapterTest { Test.startTest(); CommercePayments.ReferencedRefundRequest refundRequest = new CommercePayments.ReferencedRefundRequest(null, paymentId); CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(refundRequest, CommercePayments.RequestType.ReferencedRefund); - CommercePayments.GatewayResponse refundResponse = (CommercePayments.GatewayResponse) TestDataFactory.adyenAdapter.processRequest(context); + TestDataFactory.adyenAdapter.processRequest(context); Test.stopTest(); Assert.fail('Exception expected'); - } catch(Exception ex) { + } catch (Exception ex) { Assert.areEqual('Payment Amount Missing', ex.getMessage(), 'Payment Amount is available.'); } } diff --git a/force-app/main/default/classes/AdyenAuthorisationHelper.cls-meta.xml b/force-app/main/default/classes/AdyenAuthorisationHelper.cls-meta.xml index 754ecb1..f5e18fd 100644 --- a/force-app/main/default/classes/AdyenAuthorisationHelper.cls-meta.xml +++ b/force-app/main/default/classes/AdyenAuthorisationHelper.cls-meta.xml @@ -1,5 +1,5 @@ - 57.0 + 60.0 Active diff --git a/force-app/main/default/classes/AdyenCaptureHelper.cls b/force-app/main/default/classes/AdyenCaptureHelper.cls index 218ec0b..3ec82e0 100644 --- a/force-app/main/default/classes/AdyenCaptureHelper.cls +++ b/force-app/main/default/classes/AdyenCaptureHelper.cls @@ -36,15 +36,7 @@ public with sharing class AdyenCaptureHelper { } Adyen_Adapter__mdt adyenAdapterMdt = AdyenPaymentUtility.retrieveGatewayMetadata(adapterName); - String currencyCode = adyenAdapterMdt.Single_Currency_Code__c != null ? adyenAdapterMdt.Single_Currency_Code__c : pa.CurrencyIsoCode; - CheckoutModificationRequest modRequest = AdyenPaymentUtility.createModificationRequest( - CommercePayments.RequestType.Capture, - currencyCode, - captureRequest.amount, - adyenAdapterMdt.Merchant_Account__c, - AdyenPaymentUtility.getReference(captureRequest), - adyenAdapterMdt.System_Integrator_Name__c - ); + CheckoutModificationRequest modRequest = AdyenPaymentUtility.createModificationRequest(captureRequest, pa.CurrencyIsoCode, adyenAdapterMdt); // Line items required for partial captures for Open Invoice methods if (AdyenPaymentUtility.checkIfOpenInvoiceFromAuthorization(pa)) { @@ -56,9 +48,7 @@ public with sharing class AdyenCaptureHelper { modRequest.setLineItems(AdyenPaymentUtility.addInvoiceData(invoiceId)); } - String captureEndpointURL = adyenAdapterMdt.Capture_Endpoint__c; - captureEndpointURL = captureEndpointURL.replace('{paymentPspReference}', pspReference); - HttpResponse adyenHttpResponse = AdyenPaymentUtility.sendModificationRequest(modRequest, adyenAdapterMdt, captureEndpointURL); + HttpResponse adyenHttpResponse = AdyenPaymentUtility.sendModificationRequest(modRequest, adyenAdapterMdt, pspReference); return processCaptureResponse(adyenHttpResponse, captureRequest.amount); } @@ -76,11 +66,11 @@ public with sharing class AdyenCaptureHelper { salesforceResponse.setGatewayReferenceDetails(adyenResponse.getReference()); salesforceResponse.setGatewayResultCode(adyenResponse.getStatus()); - if (adyenResponse != null && adyenHttpResponse.getStatusCode() != AdyenConstants.HTTP_ERROR_CODE) { // HTTP connection with Adyen was successful + if (adyenHttpResponse.getStatusCode() != AdyenConstants.HTTP_ERROR_CODE) { // HTTP connection with Adyen was successful salesforceResponse.setGatewayReferenceNumber(adyenResponse.getPSPReference()); salesforceResponse.setSalesforceResultCodeInfo(AdyenConstants.SUCCESS_SALESFORCE_RESULT_CODE_INFO); if (adyenResponse.getStatus() == AdyenConstants.NOTIFICATION_RECEIVED_CHECKOUT) { - salesforceResponse.setGatewayMessage('[capture-received]'); + salesforceResponse.setGatewayMessage('[capture-received]'); } } else { salesforceResponse.setGatewayReferenceNumber(null); diff --git a/force-app/main/default/classes/AdyenOMSConstants.cls-meta.xml b/force-app/main/default/classes/AdyenOMSConstants.cls-meta.xml index fbbad0a..f5e18fd 100644 --- a/force-app/main/default/classes/AdyenOMSConstants.cls-meta.xml +++ b/force-app/main/default/classes/AdyenOMSConstants.cls-meta.xml @@ -1,5 +1,5 @@ - 56.0 + 60.0 Active diff --git a/force-app/main/default/classes/AdyenPaymentHelper.cls b/force-app/main/default/classes/AdyenPaymentHelper.cls index 8ea4df3..4aa9a64 100644 --- a/force-app/main/default/classes/AdyenPaymentHelper.cls +++ b/force-app/main/default/classes/AdyenPaymentHelper.cls @@ -12,7 +12,7 @@ public with sharing class AdyenPaymentHelper { CommercePayments.RequestType paymentRequestType = paymentGatewayContext.getPaymentRequestType(); CommercePayments.PaymentGatewayRequest paymentRequest = paymentGatewayContext.getPaymentRequest(); - if(paymentRequestType == CommercePayments.RequestType.Authorize){ + if (paymentRequestType == CommercePayments.RequestType.Authorize) { return AdyenAuthorisationHelper.authorise((CommercePayments.AuthorizationRequest)paymentRequest); } else if (paymentRequestType == CommercePayments.RequestType.Capture) { return AdyenCaptureHelper.capture((CommercePayments.CaptureRequest)paymentRequest); @@ -26,18 +26,18 @@ public with sharing class AdyenPaymentHelper { public static CommercePayments.GatewayNotificationResponse handleAsyncNotificationCallback(CommercePayments.PaymentGatewayNotificationContext gatewayNotificationContext, String apexName) { CommercePayments.PaymentGatewayNotificationRequest paymentGatewayNotificationRequest = Test.isRunningTest() ? null : gatewayNotificationContext.getPaymentGatewayNotificationRequest(); - NotificationRequestItem notificationRequestItem = parseAdyenNotificationRequest( paymentGatewayNotificationRequest ); + NotificationRequestItem notificationRequestItem = parseAdyenNotificationRequest(paymentGatewayNotificationRequest); if (notificationRequestItem.originalReference == null) { String msg = '[accepted] Notification skipped, original reference is not available'; return createNotificationResponse(msg, AdyenConstants.HTTP_SUCCESS_CODE); } - - String adapterIdFromNotificationData = AdyenPaymentUtility.retrieveApexAdapterId(notificationRequestItem.originalReference); - Id adyenAdapterId = [SELECT Id FROM ApexClass WHERE Name = :apexName AND (NamespacePrefix = :AdyenOMSConstants.ADYEN_2GP_NAMESPACE OR NamespacePrefix = '') LIMIT 1].Id; + String packageNamespace = AdyenPaymentUtility.retrieveGatewayMetadata(AdyenConstants.DEFAULT_ADAPTER_NAME).Package_Namespace__c; + Id adapterIdFromNotificationData = AdyenPaymentUtility.retrieveApexAdapterId(notificationRequestItem.originalReference); + Id adyenAdapterId = [SELECT Id FROM ApexClass WHERE Name = :apexName AND NamespacePrefix = :packageNamespace LIMIT 1].Id; Boolean isAdapterSameAsThis = adapterIdFromNotificationData == adyenAdapterId; if (isAdapterSameAsThis) { - CommercePayments.NotificationSaveResult notificationSaveResult = createNotificationSaveResult( notificationRequestItem ); + CommercePayments.NotificationSaveResult notificationSaveResult = createNotificationSaveResult(notificationRequestItem); if (notificationSaveResult != null) { return createNotificationResponse(AdyenConstants.NOTIFICATION_ACCEPTED_RESPONSE, AdyenConstants.HTTP_SUCCESS_CODE); } else { @@ -63,7 +63,6 @@ public with sharing class AdyenPaymentHelper { * @param notificationRequest The body of the Adyen notification request. * @return AdyenNotificationRequest The deserialized version of the Adyen notification request. * - * @see https://docs.adyen.com/development-resources/webhooks/understand-notifications */ public static NotificationRequestItem parseAdyenNotificationRequest(CommercePayments.PaymentGatewayNotificationRequest notificationRequest) { String adyenNotificationRequestPayload = Test.isRunningTest() ? TEST_NOTIFICATION_REQUEST_BODY : AdyenPaymentUtility.makeSalesforceCompatible(notificationRequest.getRequestBody().toString()); diff --git a/force-app/main/default/classes/AdyenPaymentUtility.cls b/force-app/main/default/classes/AdyenPaymentUtility.cls index f448025..a7e9091 100644 --- a/force-app/main/default/classes/AdyenPaymentUtility.cls +++ b/force-app/main/default/classes/AdyenPaymentUtility.cls @@ -26,22 +26,7 @@ public with sharing class AdyenPaymentUtility { * @return a Payment sObject. */ public static Payment retrievePayment(Id paymentId) { - Payment payment; - - if ( Test.isRunningTest() || ( - Schema.SObjectType.Payment.fields.Id.isAccessible() && - Schema.SObjectType.Payment.fields.GatewayRefNumber.isAccessible() && - Schema.SObjectType.Payment.fields.GatewayRefDetails.isAccessible() && - Schema.SObjectType.Payment.fields.adyenOverrideMerchantConfig__c.isAccessible() && - Schema.SObjectType.PaymentAuthorization.fields.GatewayRefNumber.isAccessible() && - Schema.SObjectType.PaymentAuthorization.fields.adyenOverrideMerchantConfig__c.isAccessible() && - Schema.SObjectType.PaymentAuthorization.fields.Adyen_Payment_Method_Variant__c.isAccessible() && - Schema.SObjectType.PaymentAuthorization.fields.Adyen_Payment_Method__c.isAccessible() && - Schema.SObjectType.Payment.fields.CurrencyIsoCode.isAccessible() && - Schema.SObjectType.OrderPaymentSummary.fields.FullName.isAccessible() && - Schema.SObjectType.SalesChannel.fields.AdyenMerchantID__c.isAccessible() - ) ) { - List payments = [ + Payment payment = [ SELECT Id, GatewayRefNumber, GatewayRefDetails, PaymentAuthorization.GatewayRefNumber, PaymentAuthorization.Adyen_Payment_Method_Variant__c, @@ -52,58 +37,28 @@ public with sharing class AdyenPaymentUtility { Payment WHERE Id = :paymentId - ]; - - if (!payments.isEmpty()) { - payment = payments[0]; - } - - } + ]; return payment; } /** * Retrieves custom meta data associated with Adyen (Endpoint info) pulls all fields. - * @param metaType name of the custom metadata type with Adyen configuration + * @param developerName name of the custom metadata type with Adyen configuration * @return Adyen_Adapter__mdt for the passed metadata type with all fields. */ - public static Adyen_Adapter__mdt retrieveGatewayMetadata(String metaType) { - if (Test.isRunningTest()) { - return new Adyen_Adapter__mdt( - MasterLabel = 'AdyenDefault', - Single_Currency_Code__c = 'USD', - System_Integrator_Name__c = 'Test integrator', - Endpoint_Method__c = 'POST', - Authorize_Endpoint__c = '/payments', - Refund_Endpoint__c = '/{paymentPspReference}/refund', - Capture_Endpoint__c = '/{paymentPspReference}/capture', - Endpoint_Api_Version__c = '/v1', - Merchant_Account__c = skipMerchantAccount ? '' : 'TEST_MERCHANT_ACCOUNT' - ); - } else { - Adyen_Adapter__mdt adyenAdapterMdt; - if ( - Schema.SObjectType.Adyen_Adapter__mdt.fields.Developername.isAccessible() && Schema.SObjectType.Adyen_Adapter__mdt.fields.NamespacePrefix.isAccessible() && - Schema.SObjectType.Adyen_Adapter__mdt.fields.MasterLabel.isAccessible() && Schema.SObjectType.Adyen_Adapter__mdt.fields.Capture_Endpoint__c.isAccessible() && - Schema.SObjectType.Adyen_Adapter__mdt.fields.Endpoint_Api_Version__c.isAccessible() && Schema.SObjectType.Adyen_Adapter__mdt.fields.Endpoint_Method__c.isAccessible() && - Schema.SObjectType.Adyen_Adapter__mdt.fields.Endpoint_Path__c.isAccessible() && Schema.SObjectType.Adyen_Adapter__mdt.fields.Merchant_Account__c.isAccessible() && - Schema.SObjectType.Adyen_Adapter__mdt.fields.System_Integrator_Name__c.isAccessible() && Schema.SObjectType.Adyen_Adapter__mdt.fields.Refund_Endpoint__c.isAccessible() && - Schema.SObjectType.Adyen_Adapter__mdt.fields.Single_Currency_Code__c.isAccessible() && Schema.SObjectType.Adyen_Adapter__mdt.fields.Authorize_Endpoint__c.isAccessible() - ) { - adyenAdapterMdt = [ - SELECT - DeveloperName, NamespacePrefix, MasterLabel, Capture_Endpoint__c, Endpoint_Api_Version__c, - System_Integrator_Name__c, Endpoint_Method__c, Endpoint_Path__c, Merchant_Account__c, - Refund_Endpoint__c, Single_Currency_Code__c, Authorize_Endpoint__c - FROM - Adyen_Adapter__mdt - WHERE - DeveloperName = :metaType - ]; - } - return adyenAdapterMdt; - } + public static Adyen_Adapter__mdt retrieveGatewayMetadata(String developerName) { + Adyen_Adapter__mdt adyenAdapterMdt = [ + SELECT + DeveloperName, Package_Namespace__c, MasterLabel, Capture_Endpoint__c, Endpoint_Api_Version__c, + System_Integrator_Name__c, Endpoint_Path__c, Merchant_Account__c, Refund_Endpoint__c, + Authorize_Endpoint__c + FROM + Adyen_Adapter__mdt + WHERE + DeveloperName = :developerName + ]; + return adyenAdapterMdt; } @@ -114,40 +69,20 @@ public with sharing class AdyenPaymentUtility { * @return a PaymentAuthorization sObject. */ public static PaymentAuthorization retrievePaymentAuthorization(Id paymentAuthId) { - PaymentAuthorization pa; - - if ( Test.isRunningTest() || ( - Schema.SObjectType.PaymentAuthorization.fields.Id.isAccessible() && - Schema.SObjectType.PaymentAuthorization.fields.PaymentAuthorizationNumber.isAccessible() && - Schema.SObjectType.PaymentAuthorization.fields.GatewayRefNumber.isAccessible() && - Schema.SObjectType.PaymentAuthorization.fields.CurrencyIsoCode.isAccessible() && - Schema.SObjectType.PaymentAuthorization.fields.adyenOverrideMerchantConfig__c.isAccessible() && - Schema.SObjectType.PaymentAuthorization.fields.Adyen_Payment_Method_Variant__c.isAccessible() && - Schema.SObjectType.PaymentGatewayLog.fields.GatewayRefNumber.isAccessible() && - Schema.SObjectType.OrderPaymentSummary.fields.Id.isAccessible() && - Schema.SObjectType.OrderPaymentSummary.fields.FullName.isAccessible() && - Schema.SObjectType.SalesChannel.fields.AdyenMerchantID__c.isAccessible()) ) - { - List paymentAuthorizations = [ - SELECT - Id, PaymentAuthorizationNumber, GatewayRefNumber, adyenOverrideMerchantConfig__c, Adyen_Payment_Method_Variant__c, - OrderPaymentSummary.LastPaymentGatewayLog.GatewayRefNumber, - OrderPaymentSummary.Id, - OrderPaymentSummary.FullName, CurrencyIsoCode, - OrderPaymentSummary.OrderSummary.Id, - OrderPaymentSummary.OrderSummary.SalesChannel.AdyenMerchantID__c - FROM - PaymentAuthorization - WHERE - Id = :paymentAuthId - ORDER BY - CreatedDate DESC - ]; - if (!paymentAuthorizations.isEmpty()) { - pa = paymentAuthorizations[0]; - } - } - return pa; + PaymentAuthorization paymentAuthorization = [ + SELECT + Id, PaymentAuthorizationNumber, GatewayRefNumber, adyenOverrideMerchantConfig__c, Adyen_Payment_Method_Variant__c, + OrderPaymentSummary.LastPaymentGatewayLog.GatewayRefNumber, + OrderPaymentSummary.Id, + OrderPaymentSummary.FullName, CurrencyIsoCode, + OrderPaymentSummary.OrderSummary.Id, + OrderPaymentSummary.OrderSummary.SalesChannel.AdyenMerchantID__c + FROM + PaymentAuthorization + WHERE + Id = :paymentAuthId + ]; + return paymentAuthorization; } /** @@ -173,8 +108,8 @@ public with sharing class AdyenPaymentUtility { * @param gatewayRefNumber original payment gateway reference number as received in the notification * @return apex class id for the payment gateway adapter. */ - public static String retrieveApexAdapterId(String gatewayRefNumber) { - String apexAdapterId = null; + public static Id retrieveApexAdapterId(String gatewayRefNumber) { + Id apexAdapterId = null; // Prioritize the payment authorization record if it exists for (PaymentAuthorization paymentAuthorization : [ @@ -327,9 +262,9 @@ public with sharing class AdyenPaymentUtility { return getRandomNumber(16); } List fulfillmentOrders = [ - SELECT FulfillmentOrderNumber - FROM FulfillmentOrder - WHERE InvoiceId = :invoiceId + SELECT FulfillmentOrderNumber + FROM FulfillmentOrder + WHERE InvoiceId = :invoiceId ]; if (fulfillmentOrders.isEmpty() || fulfillmentOrders.size() > 1) { return getRandomNumber(16); @@ -350,6 +285,7 @@ public with sharing class AdyenPaymentUtility { /** * Add application information to the webservice request * + * @param integratorName as specified in the custom metadata * @return application information map for the request. */ public static ApplicationInfo getApplicationInfo(String integratorName) { @@ -376,34 +312,54 @@ public with sharing class AdyenPaymentUtility { /** * Create a modification request by populating required properties (capture/refund) * + * @param paymentRequest capture or refund request + * @param currencyCode string containing the currency ISO code + * @param adyenAdapter custom metadata type containing the configuration * @return CheckoutModificationRequest to send to Adyen. */ - public static CheckoutModificationRequest createModificationRequest(CommercePayments.RequestType paymentType, String currencyCode, Decimal amount, String merchantAccount, String reference, String systemIntegratorName) { + public static CheckoutModificationRequest createModificationRequest(CommercePayments.PaymentGatewayRequest paymentRequest, String currencyCode, Adyen_Adapter__mdt adyenAdapter) { CheckoutModificationRequest modRequest; - if (paymentType == CommercePayments.RequestType.Capture) { + Decimal price; + String reference; + if (paymentRequest instanceof CommercePayments.CaptureRequest) { modRequest = new CheckoutCaptureRequest(); - } else if (paymentType == CommercePayments.RequestType.ReferencedRefund) { + CommercePayments.CaptureRequest captureRequest = (CommercePayments.CaptureRequest)paymentRequest; + price = captureRequest.amount; + reference = AdyenPaymentUtility.getReference(captureRequest); + } else if (paymentRequest instanceof CommercePayments.ReferencedRefundRequest) { modRequest = new CheckoutRefundRequest(); + CommercePayments.ReferencedRefundRequest refundRequest = (CommercePayments.ReferencedRefundRequest)paymentRequest; + price = refundRequest.amount; + reference = AdyenPaymentUtility.getRandomNumber(16); } - modRequest.setReference(reference); - modRequest.setMerchantAccount(merchantAccount); Amount requestAmount = new Amount(); - requestAmount.value = (amount * AdyenPaymentUtility.getAmountMultiplier(currencyCode)).longValue(); + requestAmount.value = (price * AdyenPaymentUtility.getAmountMultiplier(currencyCode)).longValue(); requestAmount.currency_x = currencyCode; + modRequest.setAmount(requestAmount); - modRequest.setApplicationInfo(AdyenPaymentUtility.getApplicationInfo(systemIntegratorName)); + modRequest.setReference(reference); + modRequest.setMerchantAccount(adyenAdapter.Merchant_Account__c); + modRequest.setApplicationInfo(AdyenPaymentUtility.getApplicationInfo(adyenAdapter.System_Integrator_Name__c)); return modRequest; } /** * Send modification request (payment/refund) to Adyen platform * + * @param modRequest request to be sent + * @param adyenAdapterMdt custom metadata used + * @param pspReference Adyen payment reference * @return response from adyen platform. */ - public static HttpResponse sendModificationRequest(CheckoutModificationRequest modRequest, Adyen_Adapter__mdt adyenAdapterMdt, String endpoint) { + public static HttpResponse sendModificationRequest(CheckoutModificationRequest modRequest, Adyen_Adapter__mdt adyenAdapterMdt, String pspReference) { + String endpoint = adyenAdapterMdt.Endpoint_Path__c + adyenAdapterMdt.Endpoint_Api_Version__c; + if (modRequest instanceof CheckoutCaptureRequest) { + endpoint += adyenAdapterMdt.Capture_Endpoint__c.replace('{paymentPspReference}', pspReference); + } else if (modRequest instanceof CheckoutRefundRequest) { + endpoint += adyenAdapterMdt.Refund_Endpoint__c.replace('{paymentPspReference}', pspReference); + } String body = AdyenPaymentUtility.makeAdyenCompatible(JSON.serialize(modRequest, true)); String apiKey = MERCHANT_API_KEY; - endpoint = Test.isRunningTest() ? TEST_ENDPOINT + endpoint : adyenAdapterMdt.Endpoint_Path__c + adyenAdapterMdt.Endpoint_Api_Version__c + endpoint; AdyenClient client = new AdyenClient(apiKey, endpoint); HttpResponse response = client.request(client.config, body); return response; @@ -412,7 +368,9 @@ public with sharing class AdyenPaymentUtility { /** * Create an AUTH request by populating required properties * - * @return AuthorisationRequest to send to Adyen. + * @param authRequest authorization request from SF Commerce Payments + * @param adyenAdapterMdt custom metadata used + * @return AuthorisationRequest to be sent to Adyen. */ public static AuthorisationRequest createAuthorisationRequest(CommercePayments.AuthorizationRequest authRequest, Adyen_Adapter__mdt adyenAdapterMdt) { AuthorisationRequest adyenAuthorisationRequest = new AuthorisationRequest(); @@ -470,6 +428,8 @@ public with sharing class AdyenPaymentUtility { /** * Send authorisation request to Adyen platform * + * @param authRequest to be sent to Adyen + * @param adyenAdapterMdt custom metadata used * @return response from adyen platform. */ public static HttpResponse sendAuthorisationRequest(AuthorisationRequest authRequest, Adyen_Adapter__mdt adyenAdapterMdt){ diff --git a/force-app/main/default/classes/AdyenPaymentUtilityTest.cls b/force-app/main/default/classes/AdyenPaymentUtilityTest.cls index 4bbe800..0488850 100644 --- a/force-app/main/default/classes/AdyenPaymentUtilityTest.cls +++ b/force-app/main/default/classes/AdyenPaymentUtilityTest.cls @@ -57,18 +57,17 @@ private class AdyenPaymentUtilityTest { @IsTest private static void createModificationRequestTest() { //given - CommercePayments.RequestType paymentType = CommercePayments.RequestType.Capture; + CommercePayments.CaptureRequest captureRequest; String currencyCode = 'USD'; Double price; // request comes as Double value - String merchantAccount = 'test_merchant'; - String reference = 'test_reference'; - String systemIntegratorName = 'test_integrator'; + Adyen_Adapter__mdt adyenAdapterMdt = AdyenPaymentUtility.retrieveGatewayMetadata(AdyenConstants.DEFAULT_ADAPTER_NAME); Decimal expectedPrice; CheckoutCaptureRequest modificationRequest; for (Integer i = 0; i < 10; i++) { price = 100.90 + (0.01 * i); + captureRequest = new CommercePayments.CaptureRequest(price, null); // when - modificationRequest = (CheckoutCaptureRequest)AdyenPaymentUtility.createModificationRequest(paymentType, currencyCode, price, merchantAccount, reference, systemIntegratorName); + modificationRequest = (CheckoutCaptureRequest)AdyenPaymentUtility.createModificationRequest(captureRequest, currencyCode, adyenAdapterMdt); // then expectedPrice = 100 * Decimal.valueOf(price); Assert.areEqual(expectedPrice.longValue(), modificationRequest.amount.value, ASSERT_PRICE_MESSAGE + price); @@ -77,8 +76,9 @@ private class AdyenPaymentUtilityTest { currencyCode = 'JOD'; for (Integer i = 0; i < 10; i++) { price = 100.990 + (0.001 * i); + captureRequest = new CommercePayments.CaptureRequest(price, null); // when - modificationRequest = (CheckoutCaptureRequest)AdyenPaymentUtility.createModificationRequest(paymentType, currencyCode, price, merchantAccount, reference, systemIntegratorName); + modificationRequest = (CheckoutCaptureRequest)AdyenPaymentUtility.createModificationRequest(captureRequest, currencyCode, adyenAdapterMdt); // then expectedPrice = 1000 * Decimal.valueOf(price); Assert.areEqual(expectedPrice.longValue(), modificationRequest.amount.value, ASSERT_PRICE_MESSAGE + price); @@ -87,8 +87,9 @@ private class AdyenPaymentUtilityTest { currencyCode = 'JPY'; for (Integer i = 0; i < 10; i++) { price = 100990 + i; + captureRequest = new CommercePayments.CaptureRequest(price, null); // when - modificationRequest = (CheckoutCaptureRequest)AdyenPaymentUtility.createModificationRequest(paymentType, currencyCode, price, merchantAccount, reference, systemIntegratorName); + modificationRequest = (CheckoutCaptureRequest)AdyenPaymentUtility.createModificationRequest(captureRequest, currencyCode, adyenAdapterMdt); // then expectedPrice = Decimal.valueOf(price); Assert.areEqual(expectedPrice.longValue(), modificationRequest.amount.value, ASSERT_PRICE_MESSAGE + price); @@ -97,17 +98,17 @@ private class AdyenPaymentUtilityTest { currencyCode = 'EUR'; for (Integer i = 0; i < 10; i++) { price = 10.990 + (0.001 * i); + captureRequest = new CommercePayments.CaptureRequest(price, null); // when - modificationRequest = (CheckoutCaptureRequest)AdyenPaymentUtility.createModificationRequest(paymentType, currencyCode, price, merchantAccount, reference, systemIntegratorName); + modificationRequest = (CheckoutCaptureRequest)AdyenPaymentUtility.createModificationRequest(captureRequest, currencyCode, adyenAdapterMdt); // then expectedPrice = 100 * Decimal.valueOf(price); Assert.areEqual(expectedPrice.longValue(), modificationRequest.amount.value, ASSERT_PRICE_MESSAGE + price); } // checking other properties Assert.areEqual(currencyCode, modificationRequest.amount.currency_x); - Assert.areEqual(reference, modificationRequest.getReference()); - Assert.areEqual(merchantAccount, modificationRequest.getMerchantAccount()); - Assert.areEqual(systemIntegratorName, modificationRequest.getApplicationInfo().externalPlatform.integrator); + Assert.areEqual(adyenAdapterMdt.Merchant_Account__c, modificationRequest.getMerchantAccount()); + Assert.areEqual(adyenAdapterMdt.System_Integrator_Name__c, modificationRequest.getApplicationInfo().externalPlatform.integrator); } @IsTest(SeeAllData=true) // for ConnectApi use only diff --git a/force-app/main/default/classes/AdyenRefundHelper.cls b/force-app/main/default/classes/AdyenRefundHelper.cls index a860391..0b41c2e 100644 --- a/force-app/main/default/classes/AdyenRefundHelper.cls +++ b/force-app/main/default/classes/AdyenRefundHelper.cls @@ -37,15 +37,8 @@ public with sharing class AdyenRefundHelper { adapterName = AdyenConstants.DEFAULT_ADAPTER_NAME; } Adyen_Adapter__mdt adyenAdapterMdt = AdyenPaymentUtility.retrieveGatewayMetadata(adapterName); - String currencyCode = adyenAdapterMdt.Single_Currency_Code__c != null ? adyenAdapterMdt.Single_Currency_Code__c : payment.CurrencyIsoCode.toUpperCase(); - String pspReference = (payment.OrderPaymentSummary.FullName == 'DeclineRefund' ? 'dummytransaction' : AdyenPaymentUtility.getRefundGatewayRefNumber(payment)); - CheckoutModificationRequest modRequest = AdyenPaymentUtility.createModificationRequest( - CommercePayments.RequestType.ReferencedRefund, - currencyCode, refundRequest.amount, - adyenAdapterMdt.Merchant_Account__c, - AdyenPaymentUtility.getRandomNumber(16), - adyenAdapterMdt.System_Integrator_Name__c - ); + String pspReference = AdyenPaymentUtility.getRefundGatewayRefNumber(payment); + CheckoutModificationRequest modRequest = AdyenPaymentUtility.createModificationRequest(refundRequest, payment.CurrencyIsoCode, adyenAdapterMdt); //Only for Paypal Refunds - Capture reference must be a substring of refund reference if (String.isNotBlank(payment.PaymentAuthorization.Adyen_Payment_Method_Variant__c)) { if (payment.PaymentAuthorization.Adyen_Payment_Method_Variant__c.equalsIgnoreCase('Paypal') && String.isNotBlank(payment.GatewayRefDetails)) { @@ -58,9 +51,7 @@ public with sharing class AdyenRefundHelper { modRequest.setLineItems(AdyenPaymentUtility.addCreditMemoData(payment.OrderPaymentSummary.OrderSummaryId)); } - String refundEndpointURL = adyenAdapterMdt.Refund_Endpoint__c; - refundEndpointURL = refundEndpointURL.replace('{paymentPspReference}', pspReference); - HttpResponse adyenHttpResponse = AdyenPaymentUtility.sendModificationRequest(modRequest, adyenAdapterMdt, refundEndpointURL); + HttpResponse adyenHttpResponse = AdyenPaymentUtility.sendModificationRequest(modRequest, adyenAdapterMdt, pspReference); return processRefundResponse(adyenHttpResponse, refundRequest.amount); } diff --git a/force-app/main/default/classes/AdyenRefundHelper.cls-meta.xml b/force-app/main/default/classes/AdyenRefundHelper.cls-meta.xml index d75b058..f5e18fd 100644 --- a/force-app/main/default/classes/AdyenRefundHelper.cls-meta.xml +++ b/force-app/main/default/classes/AdyenRefundHelper.cls-meta.xml @@ -1,5 +1,5 @@ - 51.0 + 60.0 Active diff --git a/force-app/main/default/classes/TestDataFactory.cls b/force-app/main/default/classes/TestDataFactory.cls index c9a58e6..78b7f07 100644 --- a/force-app/main/default/classes/TestDataFactory.cls +++ b/force-app/main/default/classes/TestDataFactory.cls @@ -2,11 +2,6 @@ public class TestDataFactory { // constants public static final String TEST_PSP_REFERENCE = '853587067740652G'; - public static final String TEST_PSP_REFERENCE_FAIL = '853587067740652F'; - public static final String TEST_MERCHANT_ACCOUNT = 'TEST_MERCHANT_ACCOUNT'; - public static final String TEST_MERCHANT_REFERENCE = 'TEST_MERCHANT_REFERENCE'; - public static final String TEST_SHOPPER_REFERENCE = 'TEST_SHOPPER_REFERENCE'; - public static final String TEST_CARD_SUCCESS = '4242424242424242'; public static final String TEST_PAYMENT_TOKEN = 'TEST_PAYMENT_TOKEN'; public static final String TEST_AUTH_CODE = 'TEST_AUTH_CODE'; public static final String RESULT_CODE_SUCCESS = 'Authorised'; @@ -287,39 +282,41 @@ public class TestDataFactory { String endpoint = req.getEndpoint(); Map requestBody = (Map)JSON.deserializeUntyped(req.getBody()); - - if(endpoint.containsIgnoreCase('payment')) { - Map paymentMethod = (Map)JSON.deserializeUntyped(JSON.serialize((requestBody.get('paymentMethod')))); - Object spm = requestBody.get('storePaymentMethod'); + if (endpoint.containsIgnoreCase('capture') || endpoint.containsIgnoreCase('refund')) { + amount.put('value', Integer.valueOf(TEST_AMOUNT*10)); + } else { + Map paymentMethod = (Map) JSON.deserializeUntyped(JSON.serialize((requestBody.get('paymentMethod')))); responseBody.put('resultCode', RESULT_CODE_SUCCESS); // Authorize - if(TEST_PAYMENT_TOKEN.equals(paymentMethod.get('storedPaymentMethodId'))) { // Successful Auth + if (TEST_PAYMENT_TOKEN.equals(paymentMethod.get('storedPaymentMethodId'))) { // Successful Auth additionalData.put('authCode', TEST_AUTH_CODE); - amount.put('value', Integer.valueOf(TEST_AMOUNT*10)); + amount.put('value', Integer.valueOf(TEST_AMOUNT * 10)); } else { // Failed Auth responseBody.put('resultCode', RESULT_CODE_FAIL); res.setStatusCode(AdyenConstants.HTTP_ERROR_CODE); } responseBody.put('additionalData', additionalData); - } else if(endpoint.containsIgnoreCase('capture')) { - // Capture - responseBody.put('reference', TEST_SHOPPER_REFERENCE); - if(requestBody.get('merchantAccount') != '' && requestBody.get('amount') != null) { // Successful Capture - amount.put('value', Integer.valueOf(TEST_AMOUNT*10)); - } else { // Failed Capture - res.setStatusCode(AdyenConstants.HTTP_ERROR_CODE); - } - } else if(endpoint.containsIgnoreCase('refund')) { - // Refund - responseBody.put('reference', TEST_SHOPPER_REFERENCE); - if(requestBody.get('merchantAccount') != '' && requestBody.get('amount') != null) { // Successful Refund - amount.put('value', Integer.valueOf(TEST_AMOUNT*10)); - } else { // Failed Refund - res.setStatusCode(AdyenConstants.HTTP_ERROR_CODE); - } } res.setBody(JSON.serialize(responseBody)); return res; } } + + public class FailureResponse implements HttpCalloutMock { + public HttpResponse respond(HttpRequest req) { + return mockHttpResponse(genericErrorResponse(), 400); + } + } + + public static HttpResponse mockHttpResponse(String body, Integer code) { + HttpResponse res = new HttpResponse(); + res.setHeader('Content-Type', 'text/json'); + res.setBody(body); + res.setStatusCode(code); + return res; + } + + private static String genericErrorResponse() { + return '{"status": 400, "errorCode": "702", "message": "Empty input which would have resulted in a null result.", "errorType": "validation"}'; + } } \ No newline at end of file diff --git a/force-app/main/default/customMetadata/Adyen_Adapter.AdyenDefault.md-meta.xml b/force-app/main/default/customMetadata/Adyen_Adapter.AdyenDefault.md-meta.xml index 3478fed..adc27fb 100644 --- a/force-app/main/default/customMetadata/Adyen_Adapter.AdyenDefault.md-meta.xml +++ b/force-app/main/default/customMetadata/Adyen_Adapter.AdyenDefault.md-meta.xml @@ -14,10 +14,6 @@ Endpoint_Api_Version__c /v70 - - Endpoint_Method__c - POST - Endpoint_Path__c /checkout @@ -27,12 +23,12 @@ MerchantAccountName - Refund_Endpoint__c - /payments/{paymentPspReference}/refunds + Package_Namespace__c + adyen_payment - Single_Currency_Code__c - + Refund_Endpoint__c + /payments/{paymentPspReference}/refunds System_Integrator_Name__c diff --git a/force-app/main/default/layouts/Adyen_Adapter__mdt-Adyen Adapter Layout.layout-meta.xml b/force-app/main/default/layouts/Adyen_Adapter__mdt-Adyen Adapter Layout.layout-meta.xml index 24cb1e0..406417a 100644 --- a/force-app/main/default/layouts/Adyen_Adapter__mdt-Adyen Adapter Layout.layout-meta.xml +++ b/force-app/main/default/layouts/Adyen_Adapter__mdt-Adyen Adapter Layout.layout-meta.xml @@ -14,6 +14,10 @@ Required DeveloperName + + Edit + Merchant_Account__c + @@ -24,6 +28,10 @@ Required NamespacePrefix + + Edit + Package_Namespace__c + @@ -33,10 +41,6 @@ true - - Edit - Capture_Endpoint__c - Edit Endpoint_Api_Version__c @@ -47,11 +51,7 @@ Edit - Merchant_Account__c - - - Edit - Endpoint_Method__c + Capture_Endpoint__c Edit @@ -61,16 +61,8 @@ Edit Refund_Endpoint__c - - Readonly - CreatedById - - - Edit - Single_Currency_Code__c - Edit System_Integrator_Name__c @@ -79,6 +71,10 @@ Readonly LastModifiedById + + Readonly + CreatedById + @@ -98,7 +94,7 @@ false false - 00h8c00000hNsuK + 00h7Q00000G96rR 4 0 Default diff --git a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Authorize_Endpoint__c.field-meta.xml b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Authorize_Endpoint__c.field-meta.xml index 959103d..a999874 100644 --- a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Authorize_Endpoint__c.field-meta.xml +++ b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Authorize_Endpoint__c.field-meta.xml @@ -2,12 +2,11 @@ Authorize_Endpoint__c "/authorize" - false false - SubscriberControlled + DeveloperControlled 64 false Text false - \ No newline at end of file + diff --git a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Capture_Endpoint__c.field-meta.xml b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Capture_Endpoint__c.field-meta.xml index d2a0ba2..0808ddf 100644 --- a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Capture_Endpoint__c.field-meta.xml +++ b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Capture_Endpoint__c.field-meta.xml @@ -2,9 +2,8 @@ Capture_Endpoint__c "/capture" - false false - SubscriberControlled + DeveloperControlled 64 false diff --git a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Endpoint_Api_Version__c.field-meta.xml b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Endpoint_Api_Version__c.field-meta.xml index 176573a..f9f2b7b 100644 --- a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Endpoint_Api_Version__c.field-meta.xml +++ b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Endpoint_Api_Version__c.field-meta.xml @@ -1,11 +1,10 @@ Endpoint_Api_Version__c - false - The Api Version of the Endpoint.. ie. v52 + The Api Version of the Endpoint, e.g. v52 false - SubscriberControlled - The Api Version of the Endpoint ie. v52 + DeveloperControlled + The Api Version of the Endpoint, e.g. v52 16 false diff --git a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Endpoint_Method__c.field-meta.xml b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Endpoint_Method__c.field-meta.xml deleted file mode 100644 index 2562986..0000000 --- a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Endpoint_Method__c.field-meta.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - Endpoint_Method__c - "POST" - false - The HTTP Method for the endpoint. ie. POST - false - SubscriberControlled - The HTTP Method for the endpoint. ie. POST - - 16 - false - Text - false - diff --git a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Endpoint_Path__c.field-meta.xml b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Endpoint_Path__c.field-meta.xml index 3657a62..1109233 100644 --- a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Endpoint_Path__c.field-meta.xml +++ b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Endpoint_Path__c.field-meta.xml @@ -1,10 +1,9 @@ Endpoint_Path__c - false The path to be appended to the Endpoint URI Listed in the Named Credential. false - SubscriberControlled + DeveloperControlled The path to be appended to the Endpoint URI Listed in the Named Credential. 255 diff --git a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Merchant_Account__c.field-meta.xml b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Merchant_Account__c.field-meta.xml index b057651..a970d12 100644 --- a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Merchant_Account__c.field-meta.xml +++ b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Merchant_Account__c.field-meta.xml @@ -1,7 +1,6 @@ Merchant_Account__c - false false SubscriberControlled diff --git a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Package_Namespace__c.field-meta.xml b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Package_Namespace__c.field-meta.xml new file mode 100644 index 0000000..256abf4 --- /dev/null +++ b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Package_Namespace__c.field-meta.xml @@ -0,0 +1,13 @@ + + + Package_Namespace__c + For Adyen adyen_payment namespace + false + DeveloperControlled + Do not change this value + + 15 + false + Text + false + diff --git a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Refund_Endpoint__c.field-meta.xml b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Refund_Endpoint__c.field-meta.xml index 421cfc9..022bbb4 100644 --- a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Refund_Endpoint__c.field-meta.xml +++ b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Refund_Endpoint__c.field-meta.xml @@ -2,9 +2,8 @@ Refund_Endpoint__c "/refund" - false false - SubscriberControlled + DeveloperControlled 64 false diff --git a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Single_Currency_Code__c.field-meta.xml b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Single_Currency_Code__c.field-meta.xml deleted file mode 100644 index afeb99a..0000000 --- a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Single_Currency_Code__c.field-meta.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - Single_Currency_Code__c - false - Leave blank if org is using mult-currency otherwise enter the currency ISO Code the org is using ie. USD . If null the adapter will look for xxxIsoCode fields in the org. These are placed there when enabling multi-currency - false - SubscriberControlled - Leave blank if org is using mult-currency otherwise enter the currency ISO Code the org is using ie. USD - - 4 - false - Text - false - diff --git a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/System_Integrator_Name__c.field-meta.xml b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/System_Integrator_Name__c.field-meta.xml index 999accf..6cb2ab7 100644 --- a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/System_Integrator_Name__c.field-meta.xml +++ b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/System_Integrator_Name__c.field-meta.xml @@ -1,7 +1,6 @@ System_Integrator_Name__c - false Name of the System Integrator, working with the Merchant on installing and enabling the package false SubscriberControlled diff --git a/sfdx-project.json b/sfdx-project.json index 4ad3835..4a0a866 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -17,7 +17,7 @@ ], "namespace": "adyen_payment", "sfdcLoginUrl": "https://login.salesforce.com", - "sourceApiVersion": "57.0", + "sourceApiVersion": "60.0", "packageAliases": { "Adyen Salesforce Order Management": "0Ho4T000000blPMSAY", "API Library Apex Adyen@3.0.1-1": "04tRP0000000A5pYAE", From 824928a46b115f400e0f3d390161098572abd3c9 Mon Sep 17 00:00:00 2001 From: Danilo Cardoso Date: Mon, 24 Jun 2024 16:37:44 +0200 Subject: [PATCH 08/21] Logging errors in gateway logs response field (#37) * feat: changing logic to be a bit more clear * feat: implementing capture-failed logic * fix: refactoring webhook response logic * fix: refactoring code with lint adjustments and removal of try catch with log * fix: removing System.debugs * feat: refactoring code and adding namespace field * fix: lint issue * fix: unit tests and linting * fix: more lint issues * fix: using namespace in our scratch org to run our tests * fix: sending proper error to gateway log * fix: test classes --------- Co-authored-by: daniloc --- .../default/classes/AdyenAsyncAdapter.cls | 13 ++-- .../default/classes/AdyenAsyncAdapterTest.cls | 68 ++++++++----------- 2 files changed, 35 insertions(+), 46 deletions(-) diff --git a/force-app/main/default/classes/AdyenAsyncAdapter.cls b/force-app/main/default/classes/AdyenAsyncAdapter.cls index 4f94377..9b7d0ce 100644 --- a/force-app/main/default/classes/AdyenAsyncAdapter.cls +++ b/force-app/main/default/classes/AdyenAsyncAdapter.cls @@ -7,9 +7,7 @@ * @see AdyenPaymentHelper * @see AdyenClient */ -global with sharing class AdyenAsyncAdapter implements CommercePayments.PaymentGatewayAdapter, - CommercePayments.PaymentGatewayAsyncAdapter { - +global with sharing class AdyenAsyncAdapter implements CommercePayments.PaymentGatewayAdapter, CommercePayments.PaymentGatewayAsyncAdapter { global AdyenAsyncAdapter() {} @@ -28,7 +26,11 @@ global with sharing class AdyenAsyncAdapter implements CommercePayments.Paymen * */ global CommercePayments.GatewayResponse processRequest(CommercePayments.PaymentGatewayContext paymentGatewayContext) { - return AdyenPaymentHelper.handleFulfillmentOrderStatusChange(paymentGatewayContext); + try { + return AdyenPaymentHelper.handleFulfillmentOrderStatusChange(paymentGatewayContext); + } catch (Exception ex) { + return new CommercePayments.GatewayErrorResponse('500', ex.getMessage()); + } } /** @@ -43,5 +45,4 @@ global with sharing class AdyenAsyncAdapter implements CommercePayments.Paymen } public class GatewayException extends Exception {} - -} \ No newline at end of file +} diff --git a/force-app/main/default/classes/AdyenAsyncAdapterTest.cls b/force-app/main/default/classes/AdyenAsyncAdapterTest.cls index e324a42..2d13f08 100644 --- a/force-app/main/default/classes/AdyenAsyncAdapterTest.cls +++ b/force-app/main/default/classes/AdyenAsyncAdapterTest.cls @@ -40,32 +40,26 @@ private class AdyenAsyncAdapterTest { @IsTest static void testCaptureOutboundMissingPaymentAuthorization() { Test.setMock(HttpCalloutMock.class, new TestDataFactory.EchoHttpMock()); - try { - Test.startTest(); - CommercePayments.CaptureRequest captureRequest = new CommercePayments.CaptureRequest(TestDataFactory.TEST_AMOUNT, null); - CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(captureRequest, CommercePayments.RequestType.Capture); - TestDataFactory.adyenAdapter.processRequest(context); - Test.stopTest(); - Assert.fail('Exception expected'); - } catch(Exception ex) { - Assert.isInstanceOfType(ex, System.QueryException.class, 'Payment Authorization is available'); - } + Test.startTest(); + CommercePayments.CaptureRequest captureRequest = new CommercePayments.CaptureRequest(TestDataFactory.TEST_AMOUNT, null); + CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(captureRequest, CommercePayments.RequestType.Capture); + CommercePayments.GatewayResponse gatewayResponse = TestDataFactory.adyenAdapter.processRequest(context); + Test.stopTest(); + Assert.isInstanceOfType(gatewayResponse, CommercePayments.GatewayErrorResponse.class); + Assert.isTrue(gatewayResponse.toString().containsIgnoreCase('List has no rows for assignment to SObject')); } @IsTest static void testCaptureOutboundMissingAmount() { Test.setMock(HttpCalloutMock.class, new TestDataFactory.EchoHttpMock()); Id authId = [SELECT Id FROM PaymentAuthorization ORDER BY CreatedDate DESC LIMIT 1].Id; - try { - Test.startTest(); - CommercePayments.CaptureRequest captureRequest = new CommercePayments.CaptureRequest(null, authId); - CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(captureRequest, CommercePayments.RequestType.Capture); - TestDataFactory.adyenAdapter.processRequest(context); - Test.stopTest(); - Assert.fail('Exception expected'); - } catch(Exception ex) { - Assert.areEqual('Payment Amount Missing', ex.getMessage(), 'Payment Amount is available.'); - } + Test.startTest(); + CommercePayments.CaptureRequest captureRequest = new CommercePayments.CaptureRequest(null, authId); + CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(captureRequest, CommercePayments.RequestType.Capture); + CommercePayments.GatewayResponse gatewayResponse = TestDataFactory.adyenAdapter.processRequest(context); + Test.stopTest(); + Assert.isInstanceOfType(gatewayResponse, CommercePayments.GatewayErrorResponse.class); + Assert.isTrue(gatewayResponse.toString().containsIgnoreCase('Payment Amount Missing')); } @IsTest @@ -122,31 +116,25 @@ private class AdyenAsyncAdapterTest { @IsTest static void testRefundOutboundMissingPayment() { Test.setMock(HttpCalloutMock.class, new TestDataFactory.EchoHttpMock()); - try { - Test.startTest(); - CommercePayments.ReferencedRefundRequest refundRequest = new CommercePayments.ReferencedRefundRequest(TestDataFactory.TEST_AMOUNT, null); - CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(refundRequest, CommercePayments.RequestType.ReferencedRefund); - TestDataFactory.adyenAdapter.processRequest(context); - Test.stopTest(); - Assert.fail('Exception expected'); - } catch(Exception ex) { - Assert.isInstanceOfType(ex, System.QueryException.class, 'Payment Authorization is available'); - } + Test.startTest(); + CommercePayments.ReferencedRefundRequest refundRequest = new CommercePayments.ReferencedRefundRequest(TestDataFactory.TEST_AMOUNT, null); + CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(refundRequest, CommercePayments.RequestType.ReferencedRefund); + CommercePayments.GatewayResponse gatewayResponse = TestDataFactory.adyenAdapter.processRequest(context); + Test.stopTest(); + Assert.isInstanceOfType(gatewayResponse, CommercePayments.GatewayErrorResponse.class); + Assert.isTrue(gatewayResponse.toString().containsIgnoreCase('List has no rows for assignment to SObject')); } @IsTest static void testRefundOutboundMissingAmount() { Test.setMock(HttpCalloutMock.class, new TestDataFactory.EchoHttpMock()); Id paymentId = [SELECT Id FROM Payment ORDER BY CreatedDate DESC LIMIT 1].Id; - try { - Test.startTest(); - CommercePayments.ReferencedRefundRequest refundRequest = new CommercePayments.ReferencedRefundRequest(null, paymentId); - CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(refundRequest, CommercePayments.RequestType.ReferencedRefund); - TestDataFactory.adyenAdapter.processRequest(context); - Test.stopTest(); - Assert.fail('Exception expected'); - } catch (Exception ex) { - Assert.areEqual('Payment Amount Missing', ex.getMessage(), 'Payment Amount is available.'); - } + Test.startTest(); + CommercePayments.ReferencedRefundRequest refundRequest = new CommercePayments.ReferencedRefundRequest(null, paymentId); + CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(refundRequest, CommercePayments.RequestType.ReferencedRefund); + CommercePayments.GatewayResponse gatewayResponse = TestDataFactory.adyenAdapter.processRequest(context); + Test.stopTest(); + Assert.isInstanceOfType(gatewayResponse, CommercePayments.GatewayErrorResponse.class); + Assert.isTrue(gatewayResponse.toString().containsIgnoreCase('Payment Amount Missing')); } } \ No newline at end of file From ba6fdf3c259e614da8085253585abb182717c0f7 Mon Sep 17 00:00:00 2001 From: Danilo Cardoso Date: Wed, 26 Jun 2024 13:10:09 +0200 Subject: [PATCH 09/21] Minor custom metadata layout improvement and code owners update (#38) * refactor: improving layout section of custom metadata * fix: updating code owners --------- Co-authored-by: daniloc --- .github/CODEOWNERS | 2 +- ...__mdt-Adyen Adapter Layout.layout-meta.xml | 55 +++++++++++-------- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f89d4af..172fa38 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @amihajlovski @dcardos @shanikantsingh @shubhamvijaivargiya @zenit2001 +* @amihajlovski @dcardos @shanikantsingh @shubhamvijaivargiya @zenit2001 @shubhamk67 diff --git a/force-app/main/default/layouts/Adyen_Adapter__mdt-Adyen Adapter Layout.layout-meta.xml b/force-app/main/default/layouts/Adyen_Adapter__mdt-Adyen Adapter Layout.layout-meta.xml index 406417a..a01b04a 100644 --- a/force-app/main/default/layouts/Adyen_Adapter__mdt-Adyen Adapter Layout.layout-meta.xml +++ b/force-app/main/default/layouts/Adyen_Adapter__mdt-Adyen Adapter Layout.layout-meta.xml @@ -1,76 +1,87 @@ - false - false + true + true true - + Required MasterLabel - - Required - DeveloperName - Edit Merchant_Account__c - - Edit - IsProtected - Required - NamespacePrefix + DeveloperName Edit - Package_Namespace__c + System_Integrator_Name__c - + false - false + true true - + + + Required + NamespacePrefix + + + Edit + Package_Namespace__c + + + Edit + IsProtected + Edit Endpoint_Api_Version__c + + Edit Endpoint_Path__c Edit - Capture_Endpoint__c + Authorize_Endpoint__c Edit - Authorize_Endpoint__c + Capture_Endpoint__c Edit Refund_Endpoint__c + + + + false + true + true + - - Edit - System_Integrator_Name__c - Readonly LastModifiedById + + Readonly CreatedById From 683040ae18fedb5d49ad3c80de899e85b06ed65a Mon Sep 17 00:00:00 2001 From: Danilo Cardoso Date: Thu, 11 Jul 2024 15:57:45 +0200 Subject: [PATCH 10/21] Webhook HMAC Key validation (#39) * feat: implementing HMAC key validation and refactoring code * feat: HMAC Key custom metadata field and removing unused package namespace field * fix: removing value of deleted field * fix: filling initial mock hmac key string to avoid exception --------- Co-authored-by: daniloc --- .../default/classes/AdyenAsyncAdapter.cls | 5 +- .../default/classes/AdyenAsyncAdapterTest.cls | 21 +-- .../default/classes/AdyenOMSConstants.cls | 18 +-- .../default/classes/AdyenPaymentHelper.cls | 57 ++++---- .../classes/AdyenPaymentHelperTest.cls | 50 +++++-- .../default/classes/AdyenPaymentUtility.cls | 132 +++++++++++------- .../main/default/classes/TestDataFactory.cls | 4 +- .../Adyen_Adapter.AdyenDefault.md-meta.xml | 10 +- ...__mdt-Adyen Adapter Layout.layout-meta.xml | 10 +- .../fields/HMAC_Key__c.field-meta.xml | 11 ++ .../Package_Namespace__c.field-meta.xml | 13 -- 11 files changed, 190 insertions(+), 141 deletions(-) create mode 100644 force-app/main/default/objects/Adyen_Adapter__mdt/fields/HMAC_Key__c.field-meta.xml delete mode 100644 force-app/main/default/objects/Adyen_Adapter__mdt/fields/Package_Namespace__c.field-meta.xml diff --git a/force-app/main/default/classes/AdyenAsyncAdapter.cls b/force-app/main/default/classes/AdyenAsyncAdapter.cls index 9b7d0ce..830b29f 100644 --- a/force-app/main/default/classes/AdyenAsyncAdapter.cls +++ b/force-app/main/default/classes/AdyenAsyncAdapter.cls @@ -7,7 +7,7 @@ * @see AdyenPaymentHelper * @see AdyenClient */ -global with sharing class AdyenAsyncAdapter implements CommercePayments.PaymentGatewayAdapter, CommercePayments.PaymentGatewayAsyncAdapter { +global with sharing class AdyenAsyncAdapter implements CommercePayments.PaymentGatewayAdapter, CommercePayments.PaymentGatewayAsyncAdapter { global AdyenAsyncAdapter() {} @@ -40,8 +40,7 @@ global with sharing class AdyenAsyncAdapter implements CommercePayments.Paymen * @return CommercePayments.GatewayNotificationResponse */ global CommercePayments.GatewayNotificationResponse processNotification(CommercePayments.PaymentGatewayNotificationContext gatewayNotificationContext) { - String apexName = String.valueOf(this).substring(0, String.valueOf(this).indexOf(':')); - return AdyenPaymentHelper.handleAsyncNotificationCallback(gatewayNotificationContext, apexName); + return AdyenPaymentHelper.handleAsyncNotificationCallback(gatewayNotificationContext); } public class GatewayException extends Exception {} diff --git a/force-app/main/default/classes/AdyenAsyncAdapterTest.cls b/force-app/main/default/classes/AdyenAsyncAdapterTest.cls index 2d13f08..4947cf5 100644 --- a/force-app/main/default/classes/AdyenAsyncAdapterTest.cls +++ b/force-app/main/default/classes/AdyenAsyncAdapterTest.cls @@ -27,7 +27,6 @@ private class AdyenAsyncAdapterTest { Test.setMock(HttpCalloutMock.class, new TestDataFactory.FailureResponse()); Test.startTest(); - AdyenPaymentUtility.skipMerchantAccount = true; Id authId = [SELECT Id FROM PaymentAuthorization ORDER BY CreatedDate DESC LIMIT 1].Id; CommercePayments.CaptureRequest captureRequest = new CommercePayments.CaptureRequest(TestDataFactory.TEST_AMOUNT, authId); CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(captureRequest, CommercePayments.RequestType.Capture); @@ -40,13 +39,15 @@ private class AdyenAsyncAdapterTest { @IsTest static void testCaptureOutboundMissingPaymentAuthorization() { Test.setMock(HttpCalloutMock.class, new TestDataFactory.EchoHttpMock()); + Test.startTest(); CommercePayments.CaptureRequest captureRequest = new CommercePayments.CaptureRequest(TestDataFactory.TEST_AMOUNT, null); CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(captureRequest, CommercePayments.RequestType.Capture); CommercePayments.GatewayResponse gatewayResponse = TestDataFactory.adyenAdapter.processRequest(context); Test.stopTest(); + Assert.isInstanceOfType(gatewayResponse, CommercePayments.GatewayErrorResponse.class); - Assert.isTrue(gatewayResponse.toString().containsIgnoreCase('List has no rows for assignment to SObject')); + Assert.isTrue(gatewayResponse.toString().containsIgnoreCase(AdyenPaymentUtility.NO_PAYMENT_AUTH_FOUND_BY_ID)); } @IsTest @@ -64,18 +65,7 @@ private class AdyenAsyncAdapterTest { @IsTest static void testCaptureInboundSuccess() { - AdyenPaymentHelper.TEST_NOTIFICATION_REQUEST_BODY = TestDataFactory.createNotificationRequestBody('CAPTURE', TestDataFactory.GATEWAY_REF); - - Test.startTest(); - CommercePayments.GatewayNotificationResponse captureResponse = TestDataFactory.adyenAdapter.processNotification(null); - Test.stopTest(); - - Assert.isFalse(captureResponse.toString().containsIgnoreCase('error')); - } - - @IsTest - static void testRefundInboundSuccess() { - AdyenPaymentHelper.TEST_NOTIFICATION_REQUEST_BODY = TestDataFactory.createNotificationRequestBody('REFUND', TestDataFactory.GATEWAY_REF); + AdyenPaymentHelper.TEST_NOTIFICATION_REQUEST_BODY = TestDataFactory.createNotificationRequestBody('CAPTURE', TestDataFactory.TEST_PSP_REFERENCE); Test.startTest(); CommercePayments.GatewayNotificationResponse captureResponse = TestDataFactory.adyenAdapter.processNotification(null); @@ -103,7 +93,6 @@ private class AdyenAsyncAdapterTest { Test.setMock(HttpCalloutMock.class, new TestDataFactory.FailureResponse()); Test.startTest(); - AdyenPaymentUtility.skipMerchantAccount = true; Id paymentId = [SELECT Id FROM Payment ORDER BY CreatedDate DESC LIMIT 1].Id; CommercePayments.ReferencedRefundRequest refundRequest = new CommercePayments.ReferencedRefundRequest(TestDataFactory.TEST_AMOUNT, paymentId); CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(refundRequest, CommercePayments.RequestType.ReferencedRefund); @@ -122,7 +111,7 @@ private class AdyenAsyncAdapterTest { CommercePayments.GatewayResponse gatewayResponse = TestDataFactory.adyenAdapter.processRequest(context); Test.stopTest(); Assert.isInstanceOfType(gatewayResponse, CommercePayments.GatewayErrorResponse.class); - Assert.isTrue(gatewayResponse.toString().containsIgnoreCase('List has no rows for assignment to SObject')); + Assert.isTrue(gatewayResponse.toString().containsIgnoreCase(AdyenPaymentUtility.NO_PAYMENT_FOUND_BY_ID)); } @IsTest diff --git a/force-app/main/default/classes/AdyenOMSConstants.cls b/force-app/main/default/classes/AdyenOMSConstants.cls index 4cccb54..c1f0351 100644 --- a/force-app/main/default/classes/AdyenOMSConstants.cls +++ b/force-app/main/default/classes/AdyenOMSConstants.cls @@ -6,15 +6,15 @@ public with sharing class AdyenOMSConstants { 'PYG', 'RWF', 'UGX', 'VND', 'VUV', 'XAF', 'XOF', 'XPF' }; - public static final String EXTERNAL_PLATFORM_NAME_FOR_APPINFO = 'Adyen Salesforce OMS'; - public static final String ADYEN_LIBRARY_NAME_FOR_APPINFO = 'adyen-apex-api-library'; - public static final String ADYEN_LIBRARY_VERSION_FOR_APPINFO = '3.0.1'; - public static final String MERCHANT_APP_NAME_FOR_APPINFO = 'adyen-salesforce-oms'; - public static final String MERCHANT_APP_VERSION_FOR_APPINFO = '2.1.2'; + public static final String EXTERNAL_PLATFORM_NAME_FOR_APP_INFO = 'Adyen Salesforce OMS'; + public static final String ADYEN_LIBRARY_NAME_FOR_APP_INFO = 'adyen-apex-api-library'; + public static final String ADYEN_LIBRARY_VERSION_FOR_APP_INFO = '3.0.1'; + public static final String MERCHANT_APP_NAME_FOR_APP_INFO = 'adyen-salesforce-oms'; + public static final String MERCHANT_APP_VERSION_FOR_APP_INFO = '2.1.2'; - public static final String CARD_PAYMENTMETHOD_OBJECT = 'CardPaymentMethod'; - public static final String ALTERNATIVE_PAYMENTMETHOD_OBJECT = 'AlternativePaymentMethod'; + public static final String CARD_PAYMENT_METHOD_OBJECT = 'CardPaymentMethod'; + public static final String ALTERNATIVE_PAYMENT_METHOD_OBJECT = 'AlternativePaymentMethod'; - public static final String ADYEN_2GP_NAMESPACE = 'adyen_payment'; - public static final Set OPEN_INVOICE_METHODS = new Set {'klarna', 'afterpay', 'ratepay', 'facilypay', 'zip', 'affirm', 'atome', 'walley', 'clearpay'}; + public static final Set OPEN_INVOICE_METHODS = new Set{'klarna', 'afterpay', 'ratepay', 'facilypay', 'zip', 'affirm', 'atome', 'walley', 'clearpay'}; + public static final Set VALID_NOTIFICATION_TYPES = new Set{AdyenConstants.NOTIFICATION_REQUEST_TYPE_CAPTURE, AdyenConstants.NOTIFICATION_REQUEST_TYPE_REFUND, AdyenConstants.NOTIFICATION_REQUEST_TYPE_CAPTURE_FAILED, AdyenConstants.NOTIFICATION_REQUEST_TYPE_REFUND_FAILED}; } \ No newline at end of file diff --git a/force-app/main/default/classes/AdyenPaymentHelper.cls b/force-app/main/default/classes/AdyenPaymentHelper.cls index 4aa9a64..7e954c3 100644 --- a/force-app/main/default/classes/AdyenPaymentHelper.cls +++ b/force-app/main/default/classes/AdyenPaymentHelper.cls @@ -24,36 +24,37 @@ public with sharing class AdyenPaymentHelper { } } - public static CommercePayments.GatewayNotificationResponse handleAsyncNotificationCallback(CommercePayments.PaymentGatewayNotificationContext gatewayNotificationContext, String apexName) { + public static CommercePayments.GatewayNotificationResponse handleAsyncNotificationCallback(CommercePayments.PaymentGatewayNotificationContext gatewayNotificationContext) { CommercePayments.PaymentGatewayNotificationRequest paymentGatewayNotificationRequest = Test.isRunningTest() ? null : gatewayNotificationContext.getPaymentGatewayNotificationRequest(); NotificationRequestItem notificationRequestItem = parseAdyenNotificationRequest(paymentGatewayNotificationRequest); + Adyen_Adapter__mdt adyenAdapter = AdyenPaymentUtility.retrieveAdapterByMerchantAcct(notificationRequestItem.merchantAccountCode); + HMACValidator validator = new HMACValidator(notificationRequestItem, adyenAdapter.HMAC_Key__c); - if (notificationRequestItem.originalReference == null) { - String msg = '[accepted] Notification skipped, original reference is not available'; - return createNotificationResponse(msg, AdyenConstants.HTTP_SUCCESS_CODE); + if (!Test.isRunningTest() && !validator.validateHMAC()) { + return createAcceptedNotificationResponse('not a valid notification request'); } - String packageNamespace = AdyenPaymentUtility.retrieveGatewayMetadata(AdyenConstants.DEFAULT_ADAPTER_NAME).Package_Namespace__c; - Id adapterIdFromNotificationData = AdyenPaymentUtility.retrieveApexAdapterId(notificationRequestItem.originalReference); - Id adyenAdapterId = [SELECT Id FROM ApexClass WHERE Name = :apexName AND NamespacePrefix = :packageNamespace LIMIT 1].Id; - Boolean isAdapterSameAsThis = adapterIdFromNotificationData == adyenAdapterId; - if (isAdapterSameAsThis) { - CommercePayments.NotificationSaveResult notificationSaveResult = createNotificationSaveResult(notificationRequestItem); - if (notificationSaveResult != null) { - return createNotificationResponse(AdyenConstants.NOTIFICATION_ACCEPTED_RESPONSE, AdyenConstants.HTTP_SUCCESS_CODE); - } else { - String msg = '[accepted] But unsupported notification type: ' + notificationRequestItem.eventCode; - return createNotificationResponse(msg, AdyenConstants.HTTP_SUCCESS_CODE); - } - } else { - String msg = '[accepted] But not processed - wrong payment adapter or wrong instance'; - return createNotificationResponse(msg, AdyenConstants.HTTP_SUCCESS_CODE); + + if (!AdyenPaymentUtility.isValidNotification(notificationRequestItem)) { + return createAcceptedNotificationResponse('no valid psp reference found or webhook type was ignored'); } + + if (!AdyenPaymentUtility.relatedPaymentFound(notificationRequestItem.originalReference)) { + return createAcceptedNotificationResponse('no related payment record found'); + } + + createNotificationSaveResult(notificationRequestItem); + return createAcceptedNotificationResponse(null); } - private static CommercePayments.GatewayNotificationResponse createNotificationResponse(String message, Integer httpCode) { + private static CommercePayments.GatewayNotificationResponse createAcceptedNotificationResponse(String reason) { + String responseMessage = AdyenConstants.NOTIFICATION_ACCEPTED_RESPONSE; + if (!String.isBlank(reason)) { + responseMessage += ' But not processed, ' + reason; + } + CommercePayments.GatewayNotificationResponse gatewayNotificationResponse = new CommercePayments.GatewayNotificationResponse(); - gatewayNotificationResponse.setResponseBody(Blob.valueOf(message)); - gatewayNotificationResponse.setStatusCode(httpCode); + gatewayNotificationResponse.setResponseBody(Blob.valueOf(responseMessage)); + gatewayNotificationResponse.setStatusCode(AdyenConstants.HTTP_SUCCESS_CODE); return gatewayNotificationResponse; } @@ -77,8 +78,7 @@ public with sharing class AdyenPaymentHelper { } return notificationRequestItem; } - - + /** * Creates and records (ie saves) the notification save result. * @@ -90,24 +90,25 @@ public with sharing class AdyenPaymentHelper { CommercePayments.BaseNotification notification; CommercePayments.NotificationStatus notificationStatus; CommercePayments.SalesforceResultCode notificationStatusSF; - String gatewayMessageTemplate; Boolean isCaptureRequest = AdyenConstants.NOTIFICATION_REQUEST_TYPE_CAPTURE.equalsIgnoreCase(notificationRequestItem.eventCode); Boolean isRefundRequest = AdyenConstants.NOTIFICATION_REQUEST_TYPE_REFUND.equalsIgnoreCase(notificationRequestItem.eventCode); Boolean isCaptureFailedRequest = AdyenConstants.NOTIFICATION_REQUEST_TYPE_CAPTURE_FAILED.endsWithIgnoreCase(notificationRequestItem.eventCode); + Boolean isRefundFailedRequest = AdyenConstants.NOTIFICATION_REQUEST_TYPE_REFUND_FAILED.endsWithIgnoreCase(notificationRequestItem.eventCode); + if (isCaptureRequest || isCaptureFailedRequest) { notification = new CommercePayments.CaptureNotification(); gatewayMessageTemplate = '[capture-{0}] {1}'; - } else if (isRefundRequest) { + } else if (isRefundRequest || isRefundFailedRequest) { notification = new CommercePayments.ReferencedRefundNotification(); gatewayMessageTemplate = '[refund-{0}] {1}'; } else { - return null; // unsupported notification request + throw new AdyenAsyncAdapter.GatewayException('Notification of type ' + notificationRequestItem.eventCode + ' does not match criteria'); } String result; Boolean isSuccessfulNotification = Boolean.valueOf(notificationRequestItem.success); - if (isSuccessfulNotification && !isCaptureFailedRequest) { + if (isSuccessfulNotification && !isCaptureFailedRequest && !isRefundFailedRequest) { notificationStatus = CommercePayments.NotificationStatus.Success; notificationStatusSF = CommercePayments.SalesforceResultCode.Success; result = 'complete'; diff --git a/force-app/main/default/classes/AdyenPaymentHelperTest.cls b/force-app/main/default/classes/AdyenPaymentHelperTest.cls index 7911f26..98cd93a 100644 --- a/force-app/main/default/classes/AdyenPaymentHelperTest.cls +++ b/force-app/main/default/classes/AdyenPaymentHelperTest.cls @@ -1,35 +1,67 @@ @IsTest private class AdyenPaymentHelperTest { @IsTest - static void handleAsyncNotificationCallbackSkippedTest() { - AdyenPaymentHelper.TEST_NOTIFICATION_REQUEST_BODY = TestDataFactory.createNotificationRequestBody('CAPTURE', null); + static void notValidWebhookTest() { + AdyenPaymentHelper.TEST_NOTIFICATION_REQUEST_BODY = TestDataFactory.createNotificationRequestBody(AdyenConstants.NOTIFICATION_REQUEST_TYPE_CAPTURE, null); Test.startTest(); - CommercePayments.GatewayNotificationResponse captureResponse = AdyenPaymentHelper.handleAsyncNotificationCallback(null, 'AdyenAsyncAdapter'); + CommercePayments.GatewayNotificationResponse captureResponse = AdyenPaymentHelper.handleAsyncNotificationCallback(null); Test.stopTest(); Assert.isFalse(captureResponse.toString().containsIgnoreCase('error')); } @IsTest - static void handleAsyncNotificationCallbackNotProcessedTest() { - AdyenPaymentHelper.TEST_NOTIFICATION_REQUEST_BODY = TestDataFactory.createNotificationRequestBody('CAPTURE', TestDataFactory.GATEWAY_REF); + static void noRelatedPaymentWebhookTest() { + AdyenPaymentHelper.TEST_NOTIFICATION_REQUEST_BODY = TestDataFactory.createNotificationRequestBody(AdyenConstants.NOTIFICATION_REQUEST_TYPE_CAPTURE, TestDataFactory.GATEWAY_REF); Test.startTest(); - CommercePayments.GatewayNotificationResponse captureResponse = AdyenPaymentHelper.handleAsyncNotificationCallback(null, 'AdyenAsyncAdapter'); + CommercePayments.GatewayNotificationResponse captureResponse = AdyenPaymentHelper.handleAsyncNotificationCallback(null); Test.stopTest(); Assert.isFalse(captureResponse.toString().containsIgnoreCase('error')); } @IsTest - static void createNotificationSaveResultTest() { - NotificationRequestItem nri = TestDataFactory.createNotificationRequestItem(AdyenConstants.NOTIFICATION_REQUEST_TYPE_CAPTURE, 'abc123'); + static void successWebhookTest() { + // given notification with related payment + Account acct = TestDataFactory.createAccount(); + insert acct; + TestDataFactory.insertBasicPaymentRecords(acct.Id, null); + AdyenPaymentHelper.TEST_NOTIFICATION_REQUEST_BODY = TestDataFactory.createNotificationRequestBody(AdyenConstants.NOTIFICATION_REQUEST_TYPE_CAPTURE, TestDataFactory.TEST_PSP_REFERENCE); + // when + Test.startTest(); + CommercePayments.GatewayNotificationResponse captureResponse = AdyenPaymentHelper.handleAsyncNotificationCallback(null); + Test.stopTest(); + // then + Assert.isFalse(captureResponse.toString().containsIgnoreCase('error')); + } + @IsTest + static void createNotificationSaveResultRefundTest() { + // given refund fail notification + Account acct = TestDataFactory.createAccount(); + insert acct; + TestDataFactory.insertBasicPaymentRecords(acct.Id, null); + NotificationRequestItem nri = TestDataFactory.createNotificationRequestItem(AdyenConstants.NOTIFICATION_REQUEST_TYPE_REFUND, TestDataFactory.TEST_PSP_REFERENCE); + nri.success = 'false'; + // when Test.startTest(); CommercePayments.NotificationSaveResult notificationSaveResult = AdyenPaymentHelper.createNotificationSaveResult(nri); Test.stopTest(); - + // then - no matching payment found Assert.areEqual(400, notificationSaveResult.getStatusCode()); } + + @IsTest + static void createNotificationExceptionTest() { + // given notification with unhandled type + NotificationRequestItem nri = TestDataFactory.createNotificationRequestItem(AdyenConstants.NOTIFICATION_REQUEST_TYPE_CANCEL, TestDataFactory.TEST_PSP_REFERENCE); + try { // when + AdyenPaymentHelper.createNotificationSaveResult(nri); + Assert.fail(); + } catch (Exception ex) { // then + Assert.isInstanceOfType(ex, AdyenAsyncAdapter.GatewayException.class); + } + } } \ No newline at end of file diff --git a/force-app/main/default/classes/AdyenPaymentUtility.cls b/force-app/main/default/classes/AdyenPaymentUtility.cls index a7e9091..ce6e35e 100644 --- a/force-app/main/default/classes/AdyenPaymentUtility.cls +++ b/force-app/main/default/classes/AdyenPaymentUtility.cls @@ -2,9 +2,17 @@ public with sharing class AdyenPaymentUtility { @TestVisible private static final String TEST_ENDPOINT = 'https://test.com'; + @TestVisible + private static final String NO_PAYMENT_FOUND_BY_ID = 'No Payment found with this id: '; + @TestVisible + private static final String NO_PAYMENT_AUTH_FOUND_BY_ID = 'No payment authorization found with this id: '; + @TestVisible + private static final String NO_ADYEN_ADAPTER_BY_NAME = 'No Adyen adapter found with this name: '; + @TestVisible + private static final String NO_ADYEN_ADAPTER_BY_MERCHANT = 'No Adyen adapter found for this merchant account: '; + public static final String MERCHANT_API_KEY = '{!$Credential.Password}'; - public static Boolean skipMerchantAccount = false; - + /** * Looks for the Gateway ref number on the Payment record passed in. If not found gets its from * the LastPaymentGateway log on the OrderPaymentSummary record. @@ -26,7 +34,7 @@ public with sharing class AdyenPaymentUtility { * @return a Payment sObject. */ public static Payment retrievePayment(Id paymentId) { - Payment payment = [ + List payments = [ SELECT Id, GatewayRefNumber, GatewayRefDetails, PaymentAuthorization.GatewayRefNumber, PaymentAuthorization.Adyen_Payment_Method_Variant__c, @@ -38,7 +46,10 @@ public with sharing class AdyenPaymentUtility { WHERE Id = :paymentId ]; - return payment; + if (payments.isEmpty()) { + throw new AdyenAsyncAdapter.GatewayException(NO_PAYMENT_FOUND_BY_ID + paymentId); + } + return payments[0]; } @@ -48,17 +59,44 @@ public with sharing class AdyenPaymentUtility { * @return Adyen_Adapter__mdt for the passed metadata type with all fields. */ public static Adyen_Adapter__mdt retrieveGatewayMetadata(String developerName) { - Adyen_Adapter__mdt adyenAdapterMdt = [ + List adyenAdapters = [ SELECT - DeveloperName, Package_Namespace__c, MasterLabel, Capture_Endpoint__c, Endpoint_Api_Version__c, + DeveloperName, MasterLabel, Capture_Endpoint__c, Endpoint_Api_Version__c, System_Integrator_Name__c, Endpoint_Path__c, Merchant_Account__c, Refund_Endpoint__c, - Authorize_Endpoint__c - FROM - Adyen_Adapter__mdt - WHERE - DeveloperName = :developerName + Authorize_Endpoint__c, HMAC_Key__c + FROM Adyen_Adapter__mdt + WHERE DeveloperName = :developerName ]; - return adyenAdapterMdt; + if (adyenAdapters.isEmpty()) { + throw new AdyenAsyncAdapter.GatewayException(NO_ADYEN_ADAPTER_BY_NAME + developerName); + } + return adyenAdapters[0]; + } + + public static Adyen_Adapter__mdt retrieveAdapterByMerchantAcct(String merchantAccountName) { + List adyenAdapters = [ + SELECT + DeveloperName, MasterLabel, Capture_Endpoint__c, Endpoint_Api_Version__c, + System_Integrator_Name__c, Endpoint_Path__c, Merchant_Account__c, Refund_Endpoint__c, + Authorize_Endpoint__c, HMAC_Key__c + FROM Adyen_Adapter__mdt + WHERE Merchant_Account__c = :merchantAccountName + ]; + if (adyenAdapters.isEmpty()) { + throw new AdyenAsyncAdapter.GatewayException(NO_ADYEN_ADAPTER_BY_MERCHANT + merchantAccountName); + } + return adyenAdapters[0]; + } + + public static Boolean isValidNotification(NotificationRequestItem notificationRequestItem) { + return AdyenOMSConstants.VALID_NOTIFICATION_TYPES.contains(notificationRequestItem.eventCode.toUpperCase()) + && isValidPspReference(notificationRequestItem.originalReference) + && isValidPspReference(notificationRequestItem.pspReference); + } + + // The Adyen PSP Reference number is a alphanumeric value containing 16 characters + private static Boolean isValidPspReference(String pspReference) { + return String.isNotBlank(pspReference) && pspReference.isAlphanumeric() && pspReference.length() == 16; } @@ -69,7 +107,7 @@ public with sharing class AdyenPaymentUtility { * @return a PaymentAuthorization sObject. */ public static PaymentAuthorization retrievePaymentAuthorization(Id paymentAuthId) { - PaymentAuthorization paymentAuthorization = [ + List paymentAuthorizations = [ SELECT Id, PaymentAuthorizationNumber, GatewayRefNumber, adyenOverrideMerchantConfig__c, Adyen_Payment_Method_Variant__c, OrderPaymentSummary.LastPaymentGatewayLog.GatewayRefNumber, @@ -82,7 +120,10 @@ public with sharing class AdyenPaymentUtility { WHERE Id = :paymentAuthId ]; - return paymentAuthorization; + if (paymentAuthorizations.isEmpty()) { + throw new AdyenAsyncAdapter.GatewayException(NO_PAYMENT_AUTH_FOUND_BY_ID + paymentAuthId); + } + return paymentAuthorizations[0]; } /** @@ -108,34 +149,23 @@ public with sharing class AdyenPaymentUtility { * @param gatewayRefNumber original payment gateway reference number as received in the notification * @return apex class id for the payment gateway adapter. */ - public static Id retrieveApexAdapterId(String gatewayRefNumber) { - Id apexAdapterId = null; - + public static Boolean relatedPaymentFound(String gatewayRefNumber) { // Prioritize the payment authorization record if it exists - for (PaymentAuthorization paymentAuthorization : [ + List paymentAuthorizations = [ SELECT PaymentGateway.PaymentGatewayProvider.ApexAdapter.Id FROM PaymentAuthorization WHERE GatewayRefNumber = :gatewayRefNumber - ]) { - if (paymentAuthorization.PaymentGateway.PaymentGatewayProvider.ApexAdapter!=null) { - apexAdapterId = paymentAuthorization.PaymentGateway.PaymentGatewayProvider.ApexAdapter.Id; - } + ]; + if (!paymentAuthorizations.isEmpty()) { + return true; } - // Fall back to a payment record for pre-captured transactions - if (apexAdapterId==null) { - for (Payment payment : [ - SELECT PaymentGateway.PaymentGatewayProvider.ApexAdapter.Id - FROM Payment - WHERE GatewayRefNumber = :gatewayRefNumber - ]) { - if (payment.PaymentGateway.PaymentGatewayProvider.ApexAdapter!=null) { - apexAdapterId = payment.PaymentGateway.PaymentGatewayProvider.ApexAdapter.Id; - } - } - } - - return apexAdapterId; + List payments = [ + SELECT PaymentGateway.PaymentGatewayProvider.ApexAdapter.Id + FROM Payment + WHERE GatewayRefNumber = :gatewayRefNumber + ]; + return !payments.isEmpty(); } public static Integer getAmountMultiplier(String currencyCode) { @@ -217,11 +247,7 @@ public with sharing class AdyenPaymentUtility { 'number_x' => 'number', 'group_x' => 'group' }; - String output = input; - for (String key : mapKeyToReplace.keySet()) { - output = output.replace(key, mapKeyToReplace.get(key)); - } - return output; + return replaceAttributeName(input, mapKeyToReplace); } /** @@ -232,15 +258,19 @@ public with sharing class AdyenPaymentUtility { * @return output - the same json string with *_x added back in */ public static String makeSalesforceCompatible(String input) { - String output = input; Map mapKeyToReplace = new Map{ 'recurring.recurringDetailReference' => 'recurring_recurringDetailReference', 'currency' => 'currency_x', 'number' => 'number_x', 'group' => 'group_x' }; - for (String key : mapKeyToReplace.keySet()) { - output = output.replace(key, mapKeyToReplace.get(key)); + return replaceAttributeName(input, mapKeyToReplace); + } + + private static String replaceAttributeName(String input, Map fromKeyToValueMap) { + String output = input; + for (String key : fromKeyToValueMap.keySet()) { + output = output.replace(key, fromKeyToValueMap.get(key)); } return output; } @@ -292,18 +322,18 @@ public with sharing class AdyenPaymentUtility { ApplicationInfo info = new ApplicationInfo(); ExternalPlatform exPlatform = new ExternalPlatform(); - exPlatform.name = AdyenOMSConstants.EXTERNAL_PLATFORM_NAME_FOR_APPINFO; + exPlatform.name = AdyenOMSConstants.EXTERNAL_PLATFORM_NAME_FOR_APP_INFO; exPlatform.integrator = integratorName; info.externalPlatform = exPlatform; CommonField merchantApplication = new CommonField(); - merchantApplication.name = AdyenOMSConstants.MERCHANT_APP_NAME_FOR_APPINFO; - merchantApplication.version = AdyenOMSConstants.MERCHANT_APP_VERSION_FOR_APPINFO; + merchantApplication.name = AdyenOMSConstants.MERCHANT_APP_NAME_FOR_APP_INFO; + merchantApplication.version = AdyenOMSConstants.MERCHANT_APP_VERSION_FOR_APP_INFO; info.merchantApplication = merchantApplication; CommonField adyenLibrary = new CommonField(); - adyenLibrary.name = AdyenOMSConstants.ADYEN_LIBRARY_NAME_FOR_APPINFO; - adyenLibrary.version = AdyenOMSConstants.ADYEN_LIBRARY_VERSION_FOR_APPINFO; + adyenLibrary.name = AdyenOMSConstants.ADYEN_LIBRARY_NAME_FOR_APP_INFO; + adyenLibrary.version = AdyenOMSConstants.ADYEN_LIBRARY_VERSION_FOR_APP_INFO; info.adyenLibrary = adyenLibrary; return info; @@ -389,11 +419,11 @@ public with sharing class AdyenPaymentUtility { Id recordId = paymentMethod.id; String sObjName = recordId.getSobjectType().getDescribe().getName(); //determine object name - if (sObjName == AdyenOMSConstants.CARD_PAYMENTMETHOD_OBJECT) { + if (sObjName == AdyenOMSConstants.CARD_PAYMENT_METHOD_OBJECT) { //for CardPaymentMethod : Use GatewayTokenEncrypted field to retrieve token CardPaymentMethod cpmRecord = [SELECT Id, GatewayTokenEncrypted FROM CardPaymentMethod WHERE Id = :recordId LIMIT 1]; adyenToken = cpmRecord.GatewayTokenEncrypted; - } else if (sObjName == AdyenOMSConstants.ALTERNATIVE_PAYMENTMETHOD_OBJECT) { + } else if (sObjName == AdyenOMSConstants.ALTERNATIVE_PAYMENT_METHOD_OBJECT) { //for AlternativePaymentMethod : Use GatewayToken field to retrieve token AlternativePaymentMethod apmRecord = [SELECT Id, GatewayToken FROM AlternativePaymentMethod WHERE Id = :recordId LIMIT 1]; adyenToken = apmRecord.GatewayToken; diff --git a/force-app/main/default/classes/TestDataFactory.cls b/force-app/main/default/classes/TestDataFactory.cls index 78b7f07..41a3a2f 100644 --- a/force-app/main/default/classes/TestDataFactory.cls +++ b/force-app/main/default/classes/TestDataFactory.cls @@ -6,7 +6,7 @@ public class TestDataFactory { public static final String TEST_AUTH_CODE = 'TEST_AUTH_CODE'; public static final String RESULT_CODE_SUCCESS = 'Authorised'; public static final String RESULT_CODE_FAIL = 'Failure'; - public static final String GATEWAY_REF = 'gatewayReference'; + public static final String GATEWAY_REF = '0000000000000000'; public static final Double TEST_AMOUNT = 19.99; public static final String ACTIVE_CURRENCY = [SELECT IsoCode FROM CurrencyType WHERE IsActive = TRUE LIMIT 1].IsoCode; @@ -95,7 +95,7 @@ public class TestDataFactory { nri.pspReference = GATEWAY_REF; nri.eventDate = String.valueOf(System.today()); nri.success = 'true'; - nri.merchantAccountCode = 'merchant_account_code'; + nri.merchantAccountCode = [SELECT Merchant_Account__c FROM Adyen_Adapter__mdt LIMIT 1].Merchant_Account__c; nri.originalReference = originalRef; return nri; } diff --git a/force-app/main/default/customMetadata/Adyen_Adapter.AdyenDefault.md-meta.xml b/force-app/main/default/customMetadata/Adyen_Adapter.AdyenDefault.md-meta.xml index adc27fb..dd0b968 100644 --- a/force-app/main/default/customMetadata/Adyen_Adapter.AdyenDefault.md-meta.xml +++ b/force-app/main/default/customMetadata/Adyen_Adapter.AdyenDefault.md-meta.xml @@ -20,11 +20,7 @@ Merchant_Account__c - MerchantAccountName - - - Package_Namespace__c - adyen_payment + Merchant_Account_Name Refund_Endpoint__c @@ -34,4 +30,8 @@ System_Integrator_Name__c + + HMAC_Key__c + Generated_HMAC_Key + diff --git a/force-app/main/default/layouts/Adyen_Adapter__mdt-Adyen Adapter Layout.layout-meta.xml b/force-app/main/default/layouts/Adyen_Adapter__mdt-Adyen Adapter Layout.layout-meta.xml index a01b04a..bab4ba4 100644 --- a/force-app/main/default/layouts/Adyen_Adapter__mdt-Adyen Adapter Layout.layout-meta.xml +++ b/force-app/main/default/layouts/Adyen_Adapter__mdt-Adyen Adapter Layout.layout-meta.xml @@ -11,9 +11,13 @@ MasterLabel - Edit + Required Merchant_Account__c + + Required + HMAC_Key__c + @@ -37,10 +41,6 @@ Required NamespacePrefix - - Edit - Package_Namespace__c - Edit IsProtected diff --git a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/HMAC_Key__c.field-meta.xml b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/HMAC_Key__c.field-meta.xml new file mode 100644 index 0000000..38680e6 --- /dev/null +++ b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/HMAC_Key__c.field-meta.xml @@ -0,0 +1,11 @@ + + + HMAC_Key__c + false + SubscriberControlled + + 255 + false + Text + false + diff --git a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Package_Namespace__c.field-meta.xml b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Package_Namespace__c.field-meta.xml deleted file mode 100644 index 256abf4..0000000 --- a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Package_Namespace__c.field-meta.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - Package_Namespace__c - For Adyen adyen_payment namespace - false - DeveloperControlled - Do not change this value - - 15 - false - Text - false - From 0ba43b01faade2f9d17999d109fded4f5dace1b1 Mon Sep 17 00:00:00 2001 From: Danilo Cardoso Date: Wed, 17 Jul 2024 16:45:08 +0200 Subject: [PATCH 11/21] Implementing new named credential for Adyen Checkout API Callouts (#40) * feat: implementing HMAC key validation and refactoring code * feat: HMAC Key custom metadata field and removing unused package namespace field * feat: code for implementing the new named credential * feat: metadata for the new named credential --------- Co-authored-by: daniloc --- .../default/classes/AdyenAsyncAdapterTest.cls | 14 ++- .../classes/AdyenAuthorisationHelper.cls | 81 +++++++++++- .../classes/AdyenAuthorisationHelperTest.cls | 70 +++++++++-- .../AdyenAuthorisationHelperTest.cls-meta.xml | 2 +- .../default/classes/AdyenCaptureHelper.cls | 38 ++---- .../default/classes/AdyenPaymentUtility.cls | 117 +++++------------- .../classes/AdyenPaymentUtilityTest.cls | 62 +--------- .../default/classes/AdyenRefundHelper.cls | 36 ++---- .../main/default/classes/TestDataFactory.cls | 1 + .../Adyen_API.externalCredential-meta.xml | 16 +++ .../Adyen.namedCredential-meta.xml | 12 -- .../AdyenCheckout.namedCredential-meta.xml | 18 +++ 12 files changed, 238 insertions(+), 229 deletions(-) create mode 100644 force-app/main/default/externalCredentials/Adyen_API.externalCredential-meta.xml delete mode 100644 force-app/main/default/namedCredentials/Adyen.namedCredential-meta.xml create mode 100644 force-app/main/default/namedCredentials/AdyenCheckout.namedCredential-meta.xml diff --git a/force-app/main/default/classes/AdyenAsyncAdapterTest.cls b/force-app/main/default/classes/AdyenAsyncAdapterTest.cls index 4947cf5..daf0cef 100644 --- a/force-app/main/default/classes/AdyenAsyncAdapterTest.cls +++ b/force-app/main/default/classes/AdyenAsyncAdapterTest.cls @@ -15,7 +15,7 @@ private class AdyenAsyncAdapterTest { Id authId = [SELECT Id FROM PaymentAuthorization ORDER BY CreatedDate DESC LIMIT 1].Id; CommercePayments.CaptureRequest captureRequest = new CommercePayments.CaptureRequest(TestDataFactory.TEST_AMOUNT, authId); CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(captureRequest, CommercePayments.RequestType.Capture); - CommercePayments.GatewayResponse captureResponse = (CommercePayments.GatewayResponse) TestDataFactory.adyenAdapter.processRequest(context); + CommercePayments.GatewayResponse captureResponse = TestDataFactory.adyenAdapter.processRequest(context); Test.stopTest(); Assert.isTrue(captureResponse.toString().contains('[capture-received]')); @@ -30,10 +30,11 @@ private class AdyenAsyncAdapterTest { Id authId = [SELECT Id FROM PaymentAuthorization ORDER BY CreatedDate DESC LIMIT 1].Id; CommercePayments.CaptureRequest captureRequest = new CommercePayments.CaptureRequest(TestDataFactory.TEST_AMOUNT, authId); CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(captureRequest, CommercePayments.RequestType.Capture); - CommercePayments.GatewayResponse captureResponse = (CommercePayments.GatewayResponse) TestDataFactory.adyenAdapter.processRequest(context); + CommercePayments.GatewayResponse gatewayResponse = TestDataFactory.adyenAdapter.processRequest(context); Test.stopTest(); - Assert.isTrue(captureResponse.toString().contains('SYSTEMERROR')); + Assert.isInstanceOfType(gatewayResponse, CommercePayments.GatewayErrorResponse.class); + Assert.isTrue(gatewayResponse.toString().containsIgnoreCase('400')); } @IsTest @@ -82,7 +83,7 @@ private class AdyenAsyncAdapterTest { Id paymentId = [SELECT Id FROM Payment ORDER BY CreatedDate DESC LIMIT 1].Id; CommercePayments.ReferencedRefundRequest refundRequest = new CommercePayments.ReferencedRefundRequest(TestDataFactory.TEST_AMOUNT, paymentId); CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(refundRequest, CommercePayments.RequestType.ReferencedRefund); - CommercePayments.GatewayResponse refundResponse = (CommercePayments.GatewayResponse) TestDataFactory.adyenAdapter.processRequest(context); + CommercePayments.GatewayResponse refundResponse = TestDataFactory.adyenAdapter.processRequest(context); Test.stopTest(); Assert.isTrue(refundResponse.toString().contains('received')); @@ -96,10 +97,11 @@ private class AdyenAsyncAdapterTest { Id paymentId = [SELECT Id FROM Payment ORDER BY CreatedDate DESC LIMIT 1].Id; CommercePayments.ReferencedRefundRequest refundRequest = new CommercePayments.ReferencedRefundRequest(TestDataFactory.TEST_AMOUNT, paymentId); CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(refundRequest, CommercePayments.RequestType.ReferencedRefund); - CommercePayments.GatewayResponse refundResponse = (CommercePayments.GatewayResponse) TestDataFactory.adyenAdapter.processRequest(context); + CommercePayments.GatewayResponse refundResponse = TestDataFactory.adyenAdapter.processRequest(context); Test.stopTest(); - Assert.isTrue(refundResponse.toString().contains('SYSTEMERROR')); + Assert.isInstanceOfType(refundResponse, CommercePayments.GatewayErrorResponse.class); + Assert.isTrue(refundResponse.toString().containsIgnoreCase('400')); } @IsTest diff --git a/force-app/main/default/classes/AdyenAuthorisationHelper.cls b/force-app/main/default/classes/AdyenAuthorisationHelper.cls index 48f4440..1cb2d55 100644 --- a/force-app/main/default/classes/AdyenAuthorisationHelper.cls +++ b/force-app/main/default/classes/AdyenAuthorisationHelper.cls @@ -2,15 +2,14 @@ public with sharing class AdyenAuthorisationHelper { /** * Calls Adyen service to post an AUTH request to Adyen. - * @param authRequest + * @param authRequest from CommercePayments * @return authResponse */ public static CommercePayments.GatewayResponse authorise(CommercePayments.AuthorizationRequest authRequest) { - Adyen_Adapter__mdt adyenAdapterMdt = AdyenPaymentUtility.retrieveGatewayMetadata(AdyenConstants.DEFAULT_ADAPTER_NAME); - AuthorisationRequest adyenAuthorisationRequest = AdyenPaymentUtility.createAuthorisationRequest(authRequest, adyenAdapterMdt); - HttpResponse adyenHttpResponse = AdyenPaymentUtility.sendAuthorisationRequest(adyenAuthorisationRequest, adyenAdapterMdt); + AuthorisationRequest adyenAuthorisationRequest = createAuthorisationRequest(authRequest, adyenAdapterMdt); + HttpResponse adyenHttpResponse = sendAuthorisationRequest(adyenAuthorisationRequest, adyenAdapterMdt); return processAuthResponse(adyenHttpResponse, AdyenPaymentUtility.normalizeAmount(authRequest.amount), adyenAdapterMdt.Merchant_Account__c); } @@ -41,4 +40,76 @@ public with sharing class AdyenAuthorisationHelper { } } -} \ No newline at end of file + /** + * Create an AUTH request by populating required properties + * + * @param authRequest authorization request from SF Commerce Payments + * @param adyenAdapterMdt custom metadata used + * @return AuthorisationRequest to be sent to Adyen. + */ + public static AuthorisationRequest createAuthorisationRequest(CommercePayments.AuthorizationRequest authRequest, Adyen_Adapter__mdt adyenAdapterMdt) { + AuthorisationRequest adyenAuthorisationRequest = new AuthorisationRequest(); + CommercePayments.AuthApiPaymentMethodRequest paymentMethod = authRequest.paymentMethod; + String currencyCode = authRequest.currencyIsoCode.toUpperCase(); + + Decimal authAmount = authRequest.amount; + adyenAuthorisationRequest.amount = new Amount(); + adyenAuthorisationRequest.amount.currency_x = currencyCode; + adyenAuthorisationRequest.amount.value = (authAmount * AdyenPaymentUtility.getAmountMultiplier(currencyCode)).round(System.RoundingMode.HALF_UP); + + //Use existing token to create auth request + if (paymentMethod.id != null) { + //paymentMethod.id would be a string that represents the Salesforce record id of CardPaymentMethod or AlternativePaymentMethod object + String adyenToken; + Id recordId = paymentMethod.id; + String sObjName = recordId.getSobjectType().getDescribe().getName(); //determine object name + + if (sObjName == AdyenOMSConstants.CARD_PAYMENT_METHOD_OBJECT) { + //for CardPaymentMethod : Use GatewayTokenEncrypted field to retrieve token + CardPaymentMethod cpmRecord = [SELECT Id, GatewayTokenEncrypted FROM CardPaymentMethod WHERE Id = :recordId LIMIT 1]; + adyenToken = cpmRecord.GatewayTokenEncrypted; + } else if (sObjName == AdyenOMSConstants.ALTERNATIVE_PAYMENT_METHOD_OBJECT) { + //for AlternativePaymentMethod : Use GatewayToken field to retrieve token + AlternativePaymentMethod apmRecord = [SELECT Id, GatewayToken FROM AlternativePaymentMethod WHERE Id = :recordId LIMIT 1]; + adyenToken = apmRecord.GatewayToken; + } + + CardDetails cardDetails = new CardDetails(); + cardDetails.storedPaymentMethodId = adyenToken; + adyenAuthorisationRequest.paymentMethod = cardDetails; + adyenAuthorisationRequest.shopperInteraction = AuthorisationRequest.ShopperInteractionEnum.ContAuth; + adyenAuthorisationRequest.recurringProcessingModel = AuthorisationRequest.RecurringProcessingModelEnum.CardOnFile; + + } else if (paymentMethod.cardPaymentMethod != null) { + //use new card details to create auth request + CommercePayments.CardPaymentMethodRequest cpmRequest = paymentMethod.cardPaymentMethod; + CardDetails cardDetails = new CardDetails(); + cardDetails.number_x = cpmRequest.cardNumber; + cardDetails.expiryMonth = String.valueOf(cpmRequest.expiryMonth); + cardDetails.expiryYear = String.valueOf(cpmRequest.expiryYear); + cardDetails.holderName = cpmRequest.cardHolderName; + cardDetails.cvc = cpmRequest.cvv; + adyenAuthorisationRequest.paymentMethod = cardDetails; + adyenAuthorisationRequest.shopperInteraction = AuthorisationRequest.ShopperInteractionEnum.Ecommerce; + } + + adyenAuthorisationRequest.reference = AdyenPaymentUtility.getRandomNumber(16); + adyenAuthorisationRequest.merchantAccount = adyenAdapterMdt.Merchant_Account__c; + adyenAuthorisationRequest.shopperReference = UserInfo.getUserId(); + adyenAuthorisationRequest.applicationInfo = AdyenPaymentUtility.getApplicationInfo(adyenAdapterMdt.System_Integrator_Name__c); + return adyenAuthorisationRequest; + } + + /** + * Send authorisation request to Adyen platform + * + * @param authRequest to be sent to Adyen + * @param adyenAdapterMdt custom metadata used + * @return response from adyen platform. + */ + public static HttpResponse sendAuthorisationRequest(AuthorisationRequest authRequest, Adyen_Adapter__mdt adyenAdapterMdt) { + String body = AdyenPaymentUtility.makeAdyenCompatible(JSON.serialize(authRequest, true)); + String endpoint = adyenAdapterMdt.Endpoint_Api_Version__c + adyenAdapterMdt.Authorize_Endpoint__c; + return AdyenPaymentUtility.makePostRequest(endpoint, body); + } +} diff --git a/force-app/main/default/classes/AdyenAuthorisationHelperTest.cls b/force-app/main/default/classes/AdyenAuthorisationHelperTest.cls index 0543b81..6bc2573 100644 --- a/force-app/main/default/classes/AdyenAuthorisationHelperTest.cls +++ b/force-app/main/default/classes/AdyenAuthorisationHelperTest.cls @@ -1,7 +1,59 @@ -@isTest +@IsTest private class AdyenAuthorisationHelperTest { + @IsTest + static void createAuthorisationRequestTest() { + // given + Adyen_Adapter__mdt adyenAdapterMdt = AdyenPaymentUtility.retrieveGatewayMetadata(AdyenConstants.DEFAULT_ADAPTER_NAME); + AuthorisationRequest adyenAuthRequest; + Double price; + CommercePayments.AuthorizationRequest authRequest; + Long expectedValue; + for (Integer i = 0; i < 10; i++) { + price = 1008.90 + (0.01 * i); + authRequest = TestDataFactory.createAuthorisationRequest(null, price); + authRequest.currencyIsoCode = 'USD'; + // when + adyenAuthRequest = AdyenAuthorisationHelper.createAuthorisationRequest(authRequest, adyenAdapterMdt); + // then + expectedValue = (Decimal.valueOf(price) * 100).longValue(); + Assert.areEqual(expectedValue, adyenAuthRequest.amount.value, TestDataFactory.ASSERT_PRICE_MESSAGE + price); + } + // given a higher amount of decimals + for (Integer i = 0; i < 10; i++) { + price = 1008.900 + (i * 0.001); + authRequest = TestDataFactory.createAuthorisationRequest(null, price); + authRequest.currencyIsoCode = 'USD'; + // when + adyenAuthRequest = AdyenAuthorisationHelper.createAuthorisationRequest(authRequest, adyenAdapterMdt); + // then + expectedValue = (Decimal.valueOf(price) * 100).round(RoundingMode.HALF_UP); + Assert.areEqual(expectedValue, adyenAuthRequest.amount.value, TestDataFactory.ASSERT_PRICE_MESSAGE + price); + } + // given 0 decimals currency + for (Integer i = 0; i < 10; i++) { + price = 100890 + i; + authRequest = TestDataFactory.createAuthorisationRequest(null, price); + authRequest.currencyIsoCode = 'JPY'; + // when + adyenAuthRequest = AdyenAuthorisationHelper.createAuthorisationRequest(authRequest, adyenAdapterMdt); + // then + expectedValue = Decimal.valueOf(price).longValue(); + Assert.areEqual(expectedValue, adyenAuthRequest.amount.value, TestDataFactory.ASSERT_PRICE_MESSAGE + price); + } + // given 3 decimals currency + for (Integer i = 0; i < 10; i++) { + price = 100.890 + (i * 0.001); + authRequest = TestDataFactory.createAuthorisationRequest(null, price); + authRequest.currencyIsoCode = 'JOD'; + // when + adyenAuthRequest = AdyenAuthorisationHelper.createAuthorisationRequest(authRequest, adyenAdapterMdt); + // then + expectedValue = (Decimal.valueOf(price) * 1000).longValue(); + Assert.areEqual(expectedValue, adyenAuthRequest.amount.value, TestDataFactory.ASSERT_PRICE_MESSAGE + price); + } + } - @isTest + @IsTest static void authoriseCardPaymentTest() { CardPaymentMethod cardPayMethodRec = TestDataFactory.createCardPaymentMethod(); insert cardPayMethodRec; @@ -15,7 +67,7 @@ private class AdyenAuthorisationHelperTest { Assert.isTrue(gatewayResponse.toString().contains(TestDataFactory.TEST_AUTH_CODE)); } - @isTest + @IsTest static void authoriseAlternativePaymentTest() { AlternativePaymentMethod alternativePayMethodRec = TestDataFactory.createAlternativePaymentMethod(); insert alternativePayMethodRec; @@ -29,14 +81,18 @@ private class AdyenAuthorisationHelperTest { Assert.isTrue(gatewayResponse.toString().contains(TestDataFactory.TEST_AUTH_CODE)); } - @isTest + @IsTest static void authorisePaymentFailTest() { Test.setMock(HttpCalloutMock.class, new TestDataFactory.EchoHttpMock()); Test.startTest(); - CommercePayments.GatewayResponse gatewayResponse = AdyenAuthorisationHelper.authorise(TestDataFactory.createAuthorisationRequest(null)); + try { + AdyenAuthorisationHelper.authorise(TestDataFactory.createAuthorisationRequest(null)); + Assert.fail(); + } catch (Exception ex) { + Assert.isInstanceOfType(ex, AdyenAsyncAdapter.GatewayException.class); + Assert.isTrue(ex.getMessage().containsIgnoreCase('400')); + } Test.stopTest(); - - Assert.isFalse(gatewayResponse.toString().contains(TestDataFactory.TEST_AUTH_CODE)); } } \ No newline at end of file diff --git a/force-app/main/default/classes/AdyenAuthorisationHelperTest.cls-meta.xml b/force-app/main/default/classes/AdyenAuthorisationHelperTest.cls-meta.xml index 45cccbd..3a10d2e 100644 --- a/force-app/main/default/classes/AdyenAuthorisationHelperTest.cls-meta.xml +++ b/force-app/main/default/classes/AdyenAuthorisationHelperTest.cls-meta.xml @@ -1,5 +1,5 @@ - 57.0 + 60.0 Active \ No newline at end of file diff --git a/force-app/main/default/classes/AdyenCaptureHelper.cls b/force-app/main/default/classes/AdyenCaptureHelper.cls index 3ec82e0..e5f9fb5 100644 --- a/force-app/main/default/classes/AdyenCaptureHelper.cls +++ b/force-app/main/default/classes/AdyenCaptureHelper.cls @@ -40,42 +40,26 @@ public with sharing class AdyenCaptureHelper { // Line items required for partial captures for Open Invoice methods if (AdyenPaymentUtility.checkIfOpenInvoiceFromAuthorization(pa)) { - // Get invoice id - String invoiceId = 'INVALID'; - if (captureRequest.additionalData.containsKey('invoiceId')) { - invoiceId = captureRequest.additionalData.get('invoiceId'); + String invoiceId = captureRequest.additionalData?.get('invoiceId'); + if (String.isNotBlank(invoiceId)) { + modRequest.setLineItems(AdyenPaymentUtility.addInvoiceData(invoiceId)); } - modRequest.setLineItems(AdyenPaymentUtility.addInvoiceData(invoiceId)); } - HttpResponse adyenHttpResponse = AdyenPaymentUtility.sendModificationRequest(modRequest, adyenAdapterMdt, pspReference); - return processCaptureResponse(adyenHttpResponse, captureRequest.amount); + CheckoutCaptureResponse captureResponse = (CheckoutCaptureResponse)AdyenPaymentUtility.sendModificationRequest(modRequest, adyenAdapterMdt, pspReference); + return processCaptureResponse(captureResponse, captureRequest.amount); } - /** - * @param adyenHttpResponse: Response from Adyen's api after requesting a capture/refund - * @param amount capture amount - * @return CommercePayments.GatewayResponse with populated properties. - */ - public static CommercePayments.GatewayResponse processCaptureResponse(HttpResponse adyenHttpResponse, Decimal amount) { - CheckoutCaptureResponse adyenResponse = (CheckoutCaptureResponse)JSON.deserialize(AdyenPaymentUtility.makeSalesforceCompatible(adyenHttpResponse.getBody()), CheckoutCaptureResponse.class); + public static CommercePayments.GatewayResponse processCaptureResponse(CheckoutCaptureResponse captureResponse, Decimal amount) { CommercePayments.CaptureResponse salesforceResponse = new CommercePayments.CaptureResponse(); salesforceResponse.setAsync(true); salesforceResponse.setAmount(Double.valueOf(amount)); salesforceResponse.setGatewayDate(System.now()); - salesforceResponse.setGatewayReferenceDetails(adyenResponse.getReference()); - salesforceResponse.setGatewayResultCode(adyenResponse.getStatus()); - - if (adyenHttpResponse.getStatusCode() != AdyenConstants.HTTP_ERROR_CODE) { // HTTP connection with Adyen was successful - salesforceResponse.setGatewayReferenceNumber(adyenResponse.getPSPReference()); - salesforceResponse.setSalesforceResultCodeInfo(AdyenConstants.SUCCESS_SALESFORCE_RESULT_CODE_INFO); - if (adyenResponse.getStatus() == AdyenConstants.NOTIFICATION_RECEIVED_CHECKOUT) { - salesforceResponse.setGatewayMessage('[capture-received]'); - } - } else { - salesforceResponse.setGatewayReferenceNumber(null); - salesforceResponse.setSalesforceResultCodeInfo(AdyenConstants.SYSTEM_ERROR_SALESFORCE_RESULT_CODE_INFO); - } + salesforceResponse.setGatewayReferenceDetails(captureResponse.getReference()); + salesforceResponse.setGatewayResultCode(captureResponse.getStatus()); + salesforceResponse.setGatewayReferenceNumber(captureResponse.getPSPReference()); + salesforceResponse.setSalesforceResultCodeInfo(AdyenConstants.SUCCESS_SALESFORCE_RESULT_CODE_INFO); + salesforceResponse.setGatewayMessage('[capture-received]'); return salesforceResponse; } } \ No newline at end of file diff --git a/force-app/main/default/classes/AdyenPaymentUtility.cls b/force-app/main/default/classes/AdyenPaymentUtility.cls index ce6e35e..ca0063e 100644 --- a/force-app/main/default/classes/AdyenPaymentUtility.cls +++ b/force-app/main/default/classes/AdyenPaymentUtility.cls @@ -1,7 +1,4 @@ public with sharing class AdyenPaymentUtility { - - @TestVisible - private static final String TEST_ENDPOINT = 'https://test.com'; @TestVisible private static final String NO_PAYMENT_FOUND_BY_ID = 'No Payment found with this id: '; @TestVisible @@ -11,8 +8,6 @@ public with sharing class AdyenPaymentUtility { @TestVisible private static final String NO_ADYEN_ADAPTER_BY_MERCHANT = 'No Adyen adapter found for this merchant account: '; - public static final String MERCHANT_API_KEY = '{!$Credential.Password}'; - /** * Looks for the Gateway ref number on the Payment record passed in. If not found gets its from * the LastPaymentGateway log on the OrderPaymentSummary record. @@ -381,96 +376,42 @@ public with sharing class AdyenPaymentUtility { * @param pspReference Adyen payment reference * @return response from adyen platform. */ - public static HttpResponse sendModificationRequest(CheckoutModificationRequest modRequest, Adyen_Adapter__mdt adyenAdapterMdt, String pspReference) { - String endpoint = adyenAdapterMdt.Endpoint_Path__c + adyenAdapterMdt.Endpoint_Api_Version__c; + public static CheckoutModificationResponse sendModificationRequest(CheckoutModificationRequest modRequest, Adyen_Adapter__mdt adyenAdapterMdt, String pspReference) { + String endpoint = adyenAdapterMdt.Endpoint_Api_Version__c; if (modRequest instanceof CheckoutCaptureRequest) { - endpoint += adyenAdapterMdt.Capture_Endpoint__c.replace('{paymentPspReference}', pspReference); + endpoint += adyenAdapterMdt.Capture_Endpoint__c; } else if (modRequest instanceof CheckoutRefundRequest) { - endpoint += adyenAdapterMdt.Refund_Endpoint__c.replace('{paymentPspReference}', pspReference); + endpoint += adyenAdapterMdt.Refund_Endpoint__c; } - String body = AdyenPaymentUtility.makeAdyenCompatible(JSON.serialize(modRequest, true)); - String apiKey = MERCHANT_API_KEY; - AdyenClient client = new AdyenClient(apiKey, endpoint); - HttpResponse response = client.request(client.config, body); - return response; - } - - /** - * Create an AUTH request by populating required properties - * - * @param authRequest authorization request from SF Commerce Payments - * @param adyenAdapterMdt custom metadata used - * @return AuthorisationRequest to be sent to Adyen. - */ - public static AuthorisationRequest createAuthorisationRequest(CommercePayments.AuthorizationRequest authRequest, Adyen_Adapter__mdt adyenAdapterMdt) { - AuthorisationRequest adyenAuthorisationRequest = new AuthorisationRequest(); - CommercePayments.AuthApiPaymentMethodRequest paymentMethod = authRequest.paymentMethod; - String currencyCode = authRequest.currencyIsoCode.toUpperCase(); + endpoint = endpoint.replace('{paymentPspReference}', pspReference); - Decimal authAmount = authRequest.amount; - adyenAuthorisationRequest.amount = new Amount(); - adyenAuthorisationRequest.amount.currency_x = currencyCode; - adyenAuthorisationRequest.amount.value = (authAmount * AdyenPaymentUtility.getAmountMultiplier(currencyCode)).round(System.RoundingMode.HALF_UP); + HttpResponse response = makePostRequest(endpoint, JSON.serialize(modRequest, true)); - //Use existing token to create auth request - if (paymentMethod.id != null) { - //paymentMethod.id would be a string that represents the Salesforce record id of CardPaymentMethod or AlternativePaymentMethod object - String adyenToken; - Id recordId = paymentMethod.id; - String sObjName = recordId.getSobjectType().getDescribe().getName(); //determine object name + CheckoutModificationResponse modificationResponse; + String salesforceCompatibleBody = AdyenPaymentUtility.makeSalesforceCompatible(response.getBody()); + if (modRequest instanceof CheckoutCaptureRequest) { + modificationResponse = (CheckoutCaptureResponse)JSON.deserialize(salesforceCompatibleBody, CheckoutCaptureResponse.class); + } else if (modRequest instanceof CheckoutRefundRequest) { + modificationResponse = (CheckoutRefundResponse)JSON.deserialize(salesforceCompatibleBody, CheckoutRefundResponse.class); + } + return modificationResponse; + } - if (sObjName == AdyenOMSConstants.CARD_PAYMENT_METHOD_OBJECT) { - //for CardPaymentMethod : Use GatewayTokenEncrypted field to retrieve token - CardPaymentMethod cpmRecord = [SELECT Id, GatewayTokenEncrypted FROM CardPaymentMethod WHERE Id = :recordId LIMIT 1]; - adyenToken = cpmRecord.GatewayTokenEncrypted; - } else if (sObjName == AdyenOMSConstants.ALTERNATIVE_PAYMENT_METHOD_OBJECT) { - //for AlternativePaymentMethod : Use GatewayToken field to retrieve token - AlternativePaymentMethod apmRecord = [SELECT Id, GatewayToken FROM AlternativePaymentMethod WHERE Id = :recordId LIMIT 1]; - adyenToken = apmRecord.GatewayToken; - } + public static HttpResponse makePostRequest(String endpoint, String body) { + String compatibleBody = makeAdyenCompatible(body); - CardDetails cardDetails = new CardDetails(); - cardDetails.storedPaymentMethodId = adyenToken; - adyenAuthorisationRequest.paymentMethod = cardDetails; - adyenAuthorisationRequest.shopperInteraction = AuthorisationRequest.ShopperInteractionEnum.ContAuth; - adyenAuthorisationRequest.recurringProcessingModel = AuthorisationRequest.RecurringProcessingModelEnum.CardOnFile; + HttpRequest request = new HttpRequest(); + request.setEndpoint(endpoint); + request.setMethod('POST'); + request.setHeader('Content-Type', 'application/json'); + request.setBody(compatibleBody); - } else if (paymentMethod.cardPaymentMethod != null) { - //use new card details to create auth request - CommercePayments.CardPaymentMethodRequest cpmRequest = paymentMethod.cardPaymentMethod; - CardDetails cardDetails = new CardDetails(); - cardDetails.number_x = cpmRequest.cardNumber; - cardDetails.expiryMonth = String.valueOf(cpmRequest.expiryMonth); - cardDetails.expiryYear = String.valueOf(cpmRequest.expiryYear); - cardDetails.holderName = cpmRequest.cardHolderName; - cardDetails.cvc = cpmRequest.cvv; - adyenAuthorisationRequest.paymentMethod = cardDetails; - adyenAuthorisationRequest.shopperInteraction = AuthorisationRequest.ShopperInteractionEnum.Ecommerce; + CommercePayments.PaymentsHttp paymentsHttp = new CommercePayments.PaymentsHttp(); + HttpResponse response = paymentsHttp.send(request); + if (response.getStatusCode() != 200 && response.getStatusCode() != 201) { + throw new AdyenAsyncAdapter.GatewayException('Adyen Checkout API returned: ' + response.getStatusCode() + ', body: ' + response.getBody()); + } else { + return response; } - - adyenAuthorisationRequest.reference = AdyenPaymentUtility.getRandomNumber(16); - adyenAuthorisationRequest.merchantAccount = adyenAdapterMdt.Merchant_Account__c; - adyenAuthorisationRequest.shopperReference = UserInfo.getUserId(); - adyenAuthorisationRequest.applicationInfo = AdyenPaymentUtility.getApplicationInfo(adyenAdapterMdt.System_Integrator_Name__c); - return adyenAuthorisationRequest; - } - - /** - * Send authorisation request to Adyen platform - * - * @param authRequest to be sent to Adyen - * @param adyenAdapterMdt custom metadata used - * @return response from adyen platform. - */ - public static HttpResponse sendAuthorisationRequest(AuthorisationRequest authRequest, Adyen_Adapter__mdt adyenAdapterMdt){ - HttpResponse response; - String endpoint; - String body = AdyenPaymentUtility.makeAdyenCompatible(JSON.serialize(authRequest, true)); - String apiKey = MERCHANT_API_KEY; - endpoint = Test.isRunningTest() ? TEST_ENDPOINT + adyenAdapterMdt.Authorize_Endpoint__c : adyenAdapterMdt.Endpoint_Path__c + adyenAdapterMdt.Endpoint_Api_Version__c + adyenAdapterMdt.Authorize_Endpoint__c; - AdyenClient client = new AdyenClient(apiKey, endpoint); - response = client.request(client.config, body); - return response; } - -} \ No newline at end of file +} diff --git a/force-app/main/default/classes/AdyenPaymentUtilityTest.cls b/force-app/main/default/classes/AdyenPaymentUtilityTest.cls index 0488850..c377bb0 100644 --- a/force-app/main/default/classes/AdyenPaymentUtilityTest.cls +++ b/force-app/main/default/classes/AdyenPaymentUtilityTest.cls @@ -1,59 +1,5 @@ @IsTest private class AdyenPaymentUtilityTest { - private static final String ASSERT_PRICE_MESSAGE = 'For input price of '; - @IsTest - static void createAuthorisationRequestTest() { - // given - Adyen_Adapter__mdt adyenAdapterMdt = AdyenPaymentUtility.retrieveGatewayMetadata(AdyenConstants.DEFAULT_ADAPTER_NAME); - AuthorisationRequest adyenAuthRequest; - Double price; - CommercePayments.AuthorizationRequest authRequest; - Long expectedValue; - for (Integer i = 0; i < 10; i++) { - price = 1008.90 + (0.01 * i); - authRequest = TestDataFactory.createAuthorisationRequest(null, price); - authRequest.currencyIsoCode = 'USD'; - // when - adyenAuthRequest = AdyenPaymentUtility.createAuthorisationRequest(authRequest, adyenAdapterMdt); - // then - expectedValue = (Decimal.valueOf(price) * 100).longValue(); - Assert.areEqual(expectedValue, adyenAuthRequest.amount.value, ASSERT_PRICE_MESSAGE + price); - } - // given a higher amount of decimals - for (Integer i = 0; i < 10; i++) { - price = 1008.900 + (i * 0.001); - authRequest = TestDataFactory.createAuthorisationRequest(null, price); - authRequest.currencyIsoCode = 'USD'; - // when - adyenAuthRequest = AdyenPaymentUtility.createAuthorisationRequest(authRequest, adyenAdapterMdt); - // then - expectedValue = (Decimal.valueOf(price) * 100).round(RoundingMode.HALF_UP); - Assert.areEqual(expectedValue, adyenAuthRequest.amount.value, ASSERT_PRICE_MESSAGE + price); - } - // given 0 decimals currency - for (Integer i = 0; i < 10; i++) { - price = 100890 + i; - authRequest = TestDataFactory.createAuthorisationRequest(null, price); - authRequest.currencyIsoCode = 'JPY'; - // when - adyenAuthRequest = AdyenPaymentUtility.createAuthorisationRequest(authRequest, adyenAdapterMdt); - // then - expectedValue = Decimal.valueOf(price).longValue(); - Assert.areEqual(expectedValue, adyenAuthRequest.amount.value, ASSERT_PRICE_MESSAGE + price); - } - // given 3 decimals currency - for (Integer i = 0; i < 10; i++) { - price = 100.890 + (i * 0.001); - authRequest = TestDataFactory.createAuthorisationRequest(null, price); - authRequest.currencyIsoCode = 'JOD'; - // when - adyenAuthRequest = AdyenPaymentUtility.createAuthorisationRequest(authRequest, adyenAdapterMdt); - // then - expectedValue = (Decimal.valueOf(price) * 1000).longValue(); - Assert.areEqual(expectedValue, adyenAuthRequest.amount.value, ASSERT_PRICE_MESSAGE + price); - } - } - @IsTest private static void createModificationRequestTest() { //given @@ -70,7 +16,7 @@ private class AdyenPaymentUtilityTest { modificationRequest = (CheckoutCaptureRequest)AdyenPaymentUtility.createModificationRequest(captureRequest, currencyCode, adyenAdapterMdt); // then expectedPrice = 100 * Decimal.valueOf(price); - Assert.areEqual(expectedPrice.longValue(), modificationRequest.amount.value, ASSERT_PRICE_MESSAGE + price); + Assert.areEqual(expectedPrice.longValue(), modificationRequest.amount.value, TestDataFactory.ASSERT_PRICE_MESSAGE + price); } // given 3 decimals currency currencyCode = 'JOD'; @@ -81,7 +27,7 @@ private class AdyenPaymentUtilityTest { modificationRequest = (CheckoutCaptureRequest)AdyenPaymentUtility.createModificationRequest(captureRequest, currencyCode, adyenAdapterMdt); // then expectedPrice = 1000 * Decimal.valueOf(price); - Assert.areEqual(expectedPrice.longValue(), modificationRequest.amount.value, ASSERT_PRICE_MESSAGE + price); + Assert.areEqual(expectedPrice.longValue(), modificationRequest.amount.value, TestDataFactory.ASSERT_PRICE_MESSAGE + price); } // given no decimals currency currencyCode = 'JPY'; @@ -92,7 +38,7 @@ private class AdyenPaymentUtilityTest { modificationRequest = (CheckoutCaptureRequest)AdyenPaymentUtility.createModificationRequest(captureRequest, currencyCode, adyenAdapterMdt); // then expectedPrice = Decimal.valueOf(price); - Assert.areEqual(expectedPrice.longValue(), modificationRequest.amount.value, ASSERT_PRICE_MESSAGE + price); + Assert.areEqual(expectedPrice.longValue(), modificationRequest.amount.value, TestDataFactory.ASSERT_PRICE_MESSAGE + price); } // given more decimals than necessary currencyCode = 'EUR'; @@ -103,7 +49,7 @@ private class AdyenPaymentUtilityTest { modificationRequest = (CheckoutCaptureRequest)AdyenPaymentUtility.createModificationRequest(captureRequest, currencyCode, adyenAdapterMdt); // then expectedPrice = 100 * Decimal.valueOf(price); - Assert.areEqual(expectedPrice.longValue(), modificationRequest.amount.value, ASSERT_PRICE_MESSAGE + price); + Assert.areEqual(expectedPrice.longValue(), modificationRequest.amount.value, TestDataFactory.ASSERT_PRICE_MESSAGE + price); } // checking other properties Assert.areEqual(currencyCode, modificationRequest.amount.currency_x); diff --git a/force-app/main/default/classes/AdyenRefundHelper.cls b/force-app/main/default/classes/AdyenRefundHelper.cls index 0b41c2e..f958e9b 100644 --- a/force-app/main/default/classes/AdyenRefundHelper.cls +++ b/force-app/main/default/classes/AdyenRefundHelper.cls @@ -15,13 +15,13 @@ public with sharing class AdyenRefundHelper { Payment payment = AdyenPaymentUtility.retrievePayment(refundRequest.paymentId); String errorMessage = null; - if(payment == null) { + if (payment == null) { errorMessage = 'Payment Info Missing'; } - if(refundRequest.amount == null) { + if (refundRequest.amount == null) { errorMessage = 'Payment Amount Missing'; } - if(errorMessage != null) { + if (errorMessage != null) { throw new AdyenAsyncAdapter.GatewayException(errorMessage); } // By Default, retrieve the metadata key from the order's sales channel @@ -51,34 +51,20 @@ public with sharing class AdyenRefundHelper { modRequest.setLineItems(AdyenPaymentUtility.addCreditMemoData(payment.OrderPaymentSummary.OrderSummaryId)); } - HttpResponse adyenHttpResponse = AdyenPaymentUtility.sendModificationRequest(modRequest, adyenAdapterMdt, pspReference); - return processRefundResponse(adyenHttpResponse, refundRequest.amount); + CheckoutRefundResponse refundResponse = (CheckoutRefundResponse)AdyenPaymentUtility.sendModificationRequest(modRequest, adyenAdapterMdt, pspReference); + return processRefundResponse(refundResponse, refundRequest.amount); } - /** - * @param adyenHttpResponse: Response from Adyen's api after requesting a refund - * @param amount to be refunded - * @return CommercePayments.GatewayResponse with populated properties. - */ - public static CommercePayments.GatewayResponse processRefundResponse(HttpResponse adyenHttpResponse, Decimal amount) { + public static CommercePayments.GatewayResponse processRefundResponse(CheckoutRefundResponse refundResponse, Decimal amount) { CommercePayments.ReferencedRefundResponse salesforceResponse = new CommercePayments.ReferencedRefundResponse(); - CheckoutRefundResponse adyenResponse = (CheckoutRefundResponse)JSON.deserialize(AdyenPaymentUtility.makeSalesforceCompatible(adyenHttpResponse.getBody()), CheckoutRefundResponse.class); salesforceResponse.setAsync(true); salesforceResponse.setAmount(Double.valueOf(amount)); salesforceResponse.setGatewayDate(System.now()); - salesforceResponse.setGatewayReferenceDetails(adyenResponse.getReference()); - salesforceResponse.setGatewayResultCode(adyenResponse.getStatus()); - - if (adyenResponse != null && adyenHttpResponse.getStatusCode() != AdyenConstants.HTTP_ERROR_CODE) { // HTTP connection with Adyen was successful - salesforceResponse.setGatewayReferenceNumber(adyenResponse.getPSPReference()); - salesforceResponse.setSalesforceResultCodeInfo(AdyenConstants.SUCCESS_SALESFORCE_RESULT_CODE_INFO); - if (adyenResponse.getStatus() == AdyenConstants.NOTIFICATION_RECEIVED_CHECKOUT) { - salesforceResponse.setGatewayMessage('[refund-received]'); - } - } else { - salesforceResponse.setGatewayReferenceNumber(null); - salesforceResponse.setSalesforceResultCodeInfo(AdyenConstants.SYSTEM_ERROR_SALESFORCE_RESULT_CODE_INFO); - } + salesforceResponse.setGatewayReferenceDetails(refundResponse.getReference()); + salesforceResponse.setGatewayResultCode(refundResponse.getStatus()); + salesforceResponse.setGatewayReferenceNumber(refundResponse.getPSPReference()); + salesforceResponse.setSalesforceResultCodeInfo(AdyenConstants.SUCCESS_SALESFORCE_RESULT_CODE_INFO); + salesforceResponse.setGatewayMessage('[refund-received]'); return salesforceResponse; } } \ No newline at end of file diff --git a/force-app/main/default/classes/TestDataFactory.cls b/force-app/main/default/classes/TestDataFactory.cls index 41a3a2f..cda35b3 100644 --- a/force-app/main/default/classes/TestDataFactory.cls +++ b/force-app/main/default/classes/TestDataFactory.cls @@ -9,6 +9,7 @@ public class TestDataFactory { public static final String GATEWAY_REF = '0000000000000000'; public static final Double TEST_AMOUNT = 19.99; public static final String ACTIVE_CURRENCY = [SELECT IsoCode FROM CurrencyType WHERE IsActive = TRUE LIMIT 1].IsoCode; + public static final String ASSERT_PRICE_MESSAGE = 'For input price of '; public static AdyenAsyncAdapter adyenAdapter = new AdyenAsyncAdapter(); diff --git a/force-app/main/default/externalCredentials/Adyen_API.externalCredential-meta.xml b/force-app/main/default/externalCredentials/Adyen_API.externalCredential-meta.xml new file mode 100644 index 0000000..592589a --- /dev/null +++ b/force-app/main/default/externalCredentials/Adyen_API.externalCredential-meta.xml @@ -0,0 +1,16 @@ + + + Custom + + AdyenParameterKey + NamedPrincipal + 1 + + + x-API-key + AuthHeader + {!$Credential.Adyen_API.ApiKey} + 1 + + + diff --git a/force-app/main/default/namedCredentials/Adyen.namedCredential-meta.xml b/force-app/main/default/namedCredentials/Adyen.namedCredential-meta.xml deleted file mode 100644 index c52fef0..0000000 --- a/force-app/main/default/namedCredentials/Adyen.namedCredential-meta.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - true - false - https://checkout-test.adyen.com - true - - NamedUser - Password - [YOUR-WS-USER] - [YOUR-API-KEY] - \ No newline at end of file diff --git a/force-app/main/default/namedCredentials/AdyenCheckout.namedCredential-meta.xml b/force-app/main/default/namedCredentials/AdyenCheckout.namedCredential-meta.xml new file mode 100644 index 0000000..c5c5c87 --- /dev/null +++ b/force-app/main/default/namedCredentials/AdyenCheckout.namedCredential-meta.xml @@ -0,0 +1,18 @@ + + + false + true + false + + + Url + Url + https://checkout-test.adyen.com + + + Adyen_API + ExternalCredential + Authentication + + SecuredEndpoint + From 6831105df1a7c75a825e74174291609cd7c9cc1e Mon Sep 17 00:00:00 2001 From: Danilo Cardoso Date: Tue, 23 Jul 2024 10:38:58 +0200 Subject: [PATCH 12/21] Improvements: automating manual steps and removing deprecated adyenOverrideMerchantConfig__c field (#41) * feat: metadata and code for the optimization changes. * fix: admin profile - only necessary metadata * fix: adding manifest * fix: running tests synchronously * fix: making consistent package.xml --------- Co-authored-by: daniloc Co-authored-by: daniloc <> --- .github/workflows/unit-tests.yml | 2 +- .../default/classes/AdyenAsyncAdapter.cls | 2 +- .../classes/AdyenAuthorisationHelper.cls | 2 +- .../AdyenAuthorisationHelperTest.cls-meta.xml | 2 +- .../default/classes/AdyenCaptureHelper.cls | 47 +- .../default/classes/AdyenPaymentHelper.cls | 12 +- .../classes/AdyenPaymentHelperTest.cls | 14 +- .../AdyenPaymentHelperTest.cls-meta.xml | 2 +- .../default/classes/AdyenPaymentUtility.cls | 74 +- .../default/classes/AdyenRefundHelper.cls | 42 +- .../classes/TestDataFactory.cls-meta.xml | 2 +- ....gatewayProviderPaymentMethodType-meta.xml | 8 + .../Payment-Payment Layout.layout-meta.xml | 254 ------ ...yment Authorization Layout.layout-meta.xml | 193 ---- ...annel-Sales Channel Layout.layout-meta.xml | 51 -- .../Endpoint_Api_Version__c.field-meta.xml | 4 +- ...rnative_Payment_Method.recordType-meta.xml | 6 + ...enOverrideMerchantConfig__c.field-meta.xml | 10 - ...enOverrideMerchantConfig__c.field-meta.xml | 10 - .../fields/AdyenMerchantID__c.field-meta.xml | 2 +- .../Adyen.paymentGatewayProvider-meta.xml | 6 + .../default/profiles/Admin.profile-meta.xml | 841 ++---------------- manifest/package.xml | 74 ++ 23 files changed, 262 insertions(+), 1398 deletions(-) create mode 100644 force-app/main/default/gatewayProviderPaymentMethodTypes/AlternativePaymentMethod.gatewayProviderPaymentMethodType-meta.xml delete mode 100644 force-app/main/default/layouts/Payment-Payment Layout.layout-meta.xml delete mode 100644 force-app/main/default/layouts/PaymentAuthorization-Payment Authorization Layout.layout-meta.xml delete mode 100644 force-app/main/default/layouts/SalesChannel-Sales Channel Layout.layout-meta.xml create mode 100644 force-app/main/default/objects/AlternativePaymentMethod/recordTypes/Alternative_Payment_Method.recordType-meta.xml delete mode 100644 force-app/main/default/objects/Payment/fields/adyenOverrideMerchantConfig__c.field-meta.xml delete mode 100644 force-app/main/default/objects/PaymentAuthorization/fields/adyenOverrideMerchantConfig__c.field-meta.xml create mode 100644 force-app/main/default/paymentGatewayProviders/Adyen.paymentGatewayProvider-meta.xml create mode 100644 manifest/package.xml diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 204de45..f1e3f69 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -53,7 +53,7 @@ jobs: run: sf project deploy start --target-org ScratchOrg --ignore-conflicts - name: Run Apex tests - run: sf apex run test --target-org ScratchOrg --code-coverage -w 5 + run: sf apex run test --target-org ScratchOrg --code-coverage --synchronous - name: Delete Scratch Org if: always() diff --git a/force-app/main/default/classes/AdyenAsyncAdapter.cls b/force-app/main/default/classes/AdyenAsyncAdapter.cls index 830b29f..e82068b 100644 --- a/force-app/main/default/classes/AdyenAsyncAdapter.cls +++ b/force-app/main/default/classes/AdyenAsyncAdapter.cls @@ -44,4 +44,4 @@ global with sharing class AdyenAsyncAdapter implements CommercePayments.PaymentG } public class GatewayException extends Exception {} -} +} \ No newline at end of file diff --git a/force-app/main/default/classes/AdyenAuthorisationHelper.cls b/force-app/main/default/classes/AdyenAuthorisationHelper.cls index 1cb2d55..be05ca5 100644 --- a/force-app/main/default/classes/AdyenAuthorisationHelper.cls +++ b/force-app/main/default/classes/AdyenAuthorisationHelper.cls @@ -112,4 +112,4 @@ public with sharing class AdyenAuthorisationHelper { String endpoint = adyenAdapterMdt.Endpoint_Api_Version__c + adyenAdapterMdt.Authorize_Endpoint__c; return AdyenPaymentUtility.makePostRequest(endpoint, body); } -} +} \ No newline at end of file diff --git a/force-app/main/default/classes/AdyenAuthorisationHelperTest.cls-meta.xml b/force-app/main/default/classes/AdyenAuthorisationHelperTest.cls-meta.xml index 3a10d2e..f5e18fd 100644 --- a/force-app/main/default/classes/AdyenAuthorisationHelperTest.cls-meta.xml +++ b/force-app/main/default/classes/AdyenAuthorisationHelperTest.cls-meta.xml @@ -2,4 +2,4 @@ 60.0 Active - \ No newline at end of file + diff --git a/force-app/main/default/classes/AdyenCaptureHelper.cls b/force-app/main/default/classes/AdyenCaptureHelper.cls index e5f9fb5..7b84443 100644 --- a/force-app/main/default/classes/AdyenCaptureHelper.cls +++ b/force-app/main/default/classes/AdyenCaptureHelper.cls @@ -6,51 +6,42 @@ public with sharing class AdyenCaptureHelper { * @return `CommercePayments.GatewayResponse` */ public static CommercePayments.GatewayResponse capture(CommercePayments.CaptureRequest captureRequest) { - // Retrieve the PaymentAuthorization - PaymentAuthorization pa = AdyenPaymentUtility.retrievePaymentAuthorization(captureRequest.paymentAuthorizationId); + PaymentAuthorization paymentAuth = AdyenPaymentUtility.retrievePaymentAuthorization(captureRequest.paymentAuthorizationId); - String errorMessage = null; - if(pa == null) { + String errorMessage; + if (paymentAuth == null) { errorMessage = 'Payment Authorization Missing'; - } - if(captureRequest.amount == null) { - errorMessage = 'Payment Amount Missing'; - } - String pspReference = AdyenPaymentUtility.getCaptureGatewayRefNumber(pa); - if(String.isBlank(pspReference)) { + } else if (String.isBlank(paymentAuth.GatewayRefNumber)) { errorMessage = 'PspReference Missing'; + } else if (captureRequest.amount == null) { + errorMessage = 'Payment Amount Missing'; } - if(errorMessage != null) { + if (String.isNotBlank(errorMessage)) { throw new AdyenAsyncAdapter.GatewayException(errorMessage); } - // By Default, retrieve the metadata key from the order's sales channel - String adapterName = pa.OrderPaymentSummary.OrderSummary.SalesChannel.AdyenMerchantID__c; - - // Override config for this specific PaymentAuthorization - if (String.isNotBlank(pa.adyenOverrideMerchantConfig__c)) { - adapterName = pa.adyenOverrideMerchantConfig__c; - } - if (String.isBlank(adapterName)) { - adapterName = AdyenConstants.DEFAULT_ADAPTER_NAME; - } + String pspReference = paymentAuth.GatewayRefNumber; + String merchantAccount = paymentAuth.OrderPaymentSummary.OrderSummary.SalesChannel.AdyenMerchantID__c; + Adyen_Adapter__mdt adyenAdapterMdt = AdyenPaymentUtility.chooseAdapterWithFallBack(merchantAccount); - Adyen_Adapter__mdt adyenAdapterMdt = AdyenPaymentUtility.retrieveGatewayMetadata(adapterName); - CheckoutModificationRequest modRequest = AdyenPaymentUtility.createModificationRequest(captureRequest, pa.CurrencyIsoCode, adyenAdapterMdt); + CheckoutModificationRequest modRequest = createCaptureRequest(captureRequest, paymentAuth, adyenAdapterMdt); + CheckoutCaptureResponse captureResponse = (CheckoutCaptureResponse)AdyenPaymentUtility.sendModificationRequest(modRequest, adyenAdapterMdt, pspReference); + return processCaptureResponse(captureResponse, captureRequest.amount); + } + private static CheckoutCaptureRequest createCaptureRequest(CommercePayments.CaptureRequest captureRequest, PaymentAuthorization paymentAuth, Adyen_Adapter__mdt adyenAdapter) { + CheckoutCaptureRequest modRequest = (CheckoutCaptureRequest)AdyenPaymentUtility.createModificationRequest(captureRequest, paymentAuth.CurrencyIsoCode, adyenAdapter); // Line items required for partial captures for Open Invoice methods - if (AdyenPaymentUtility.checkIfOpenInvoiceFromAuthorization(pa)) { + if (AdyenPaymentUtility.checkIfOpenInvoiceFromAuthorization(paymentAuth)) { String invoiceId = captureRequest.additionalData?.get('invoiceId'); if (String.isNotBlank(invoiceId)) { modRequest.setLineItems(AdyenPaymentUtility.addInvoiceData(invoiceId)); } } - - CheckoutCaptureResponse captureResponse = (CheckoutCaptureResponse)AdyenPaymentUtility.sendModificationRequest(modRequest, adyenAdapterMdt, pspReference); - return processCaptureResponse(captureResponse, captureRequest.amount); + return modRequest; } - public static CommercePayments.GatewayResponse processCaptureResponse(CheckoutCaptureResponse captureResponse, Decimal amount) { + private static CommercePayments.GatewayResponse processCaptureResponse(CheckoutCaptureResponse captureResponse, Decimal amount) { CommercePayments.CaptureResponse salesforceResponse = new CommercePayments.CaptureResponse(); salesforceResponse.setAsync(true); salesforceResponse.setAmount(Double.valueOf(amount)); diff --git a/force-app/main/default/classes/AdyenPaymentHelper.cls b/force-app/main/default/classes/AdyenPaymentHelper.cls index 7e954c3..c38787f 100644 --- a/force-app/main/default/classes/AdyenPaymentHelper.cls +++ b/force-app/main/default/classes/AdyenPaymentHelper.cls @@ -28,10 +28,14 @@ public with sharing class AdyenPaymentHelper { CommercePayments.PaymentGatewayNotificationRequest paymentGatewayNotificationRequest = Test.isRunningTest() ? null : gatewayNotificationContext.getPaymentGatewayNotificationRequest(); NotificationRequestItem notificationRequestItem = parseAdyenNotificationRequest(paymentGatewayNotificationRequest); Adyen_Adapter__mdt adyenAdapter = AdyenPaymentUtility.retrieveAdapterByMerchantAcct(notificationRequestItem.merchantAccountCode); - HMACValidator validator = new HMACValidator(notificationRequestItem, adyenAdapter.HMAC_Key__c); - - if (!Test.isRunningTest() && !validator.validateHMAC()) { - return createAcceptedNotificationResponse('not a valid notification request'); + HMACValidator validator; + try { + validator = new HMACValidator(notificationRequestItem, adyenAdapter.HMAC_Key__c); + if (!Test.isRunningTest() && !validator.validateHMAC()) { + return createAcceptedNotificationResponse('not a valid notification request'); + } + } catch (HMACValidator.HmacValidationException hmacValidationException) { + return createAcceptedNotificationResponse(hmacValidationException.getMessage()); } if (!AdyenPaymentUtility.isValidNotification(notificationRequestItem)) { diff --git a/force-app/main/default/classes/AdyenPaymentHelperTest.cls b/force-app/main/default/classes/AdyenPaymentHelperTest.cls index 98cd93a..f5fb43d 100644 --- a/force-app/main/default/classes/AdyenPaymentHelperTest.cls +++ b/force-app/main/default/classes/AdyenPaymentHelperTest.cls @@ -5,10 +5,10 @@ private class AdyenPaymentHelperTest { AdyenPaymentHelper.TEST_NOTIFICATION_REQUEST_BODY = TestDataFactory.createNotificationRequestBody(AdyenConstants.NOTIFICATION_REQUEST_TYPE_CAPTURE, null); Test.startTest(); - CommercePayments.GatewayNotificationResponse captureResponse = AdyenPaymentHelper.handleAsyncNotificationCallback(null); + CommercePayments.GatewayNotificationResponse notificationResponse = AdyenPaymentHelper.handleAsyncNotificationCallback(null); Test.stopTest(); - Assert.isFalse(captureResponse.toString().containsIgnoreCase('error')); + Assert.isFalse(notificationResponse.toString().containsIgnoreCase('error')); } @IsTest @@ -16,10 +16,10 @@ private class AdyenPaymentHelperTest { AdyenPaymentHelper.TEST_NOTIFICATION_REQUEST_BODY = TestDataFactory.createNotificationRequestBody(AdyenConstants.NOTIFICATION_REQUEST_TYPE_CAPTURE, TestDataFactory.GATEWAY_REF); Test.startTest(); - CommercePayments.GatewayNotificationResponse captureResponse = AdyenPaymentHelper.handleAsyncNotificationCallback(null); + CommercePayments.GatewayNotificationResponse notificationResponse = AdyenPaymentHelper.handleAsyncNotificationCallback(null); Test.stopTest(); - Assert.isFalse(captureResponse.toString().containsIgnoreCase('error')); + Assert.isFalse(notificationResponse.toString().containsIgnoreCase('error')); } @IsTest @@ -31,10 +31,10 @@ private class AdyenPaymentHelperTest { AdyenPaymentHelper.TEST_NOTIFICATION_REQUEST_BODY = TestDataFactory.createNotificationRequestBody(AdyenConstants.NOTIFICATION_REQUEST_TYPE_CAPTURE, TestDataFactory.TEST_PSP_REFERENCE); // when Test.startTest(); - CommercePayments.GatewayNotificationResponse captureResponse = AdyenPaymentHelper.handleAsyncNotificationCallback(null); + CommercePayments.GatewayNotificationResponse notificationResponse = AdyenPaymentHelper.handleAsyncNotificationCallback(null); Test.stopTest(); // then - Assert.isFalse(captureResponse.toString().containsIgnoreCase('error')); + Assert.isFalse(notificationResponse.toString().containsIgnoreCase('error')); } @IsTest @@ -64,4 +64,4 @@ private class AdyenPaymentHelperTest { Assert.isInstanceOfType(ex, AdyenAsyncAdapter.GatewayException.class); } } -} \ No newline at end of file +} diff --git a/force-app/main/default/classes/AdyenPaymentHelperTest.cls-meta.xml b/force-app/main/default/classes/AdyenPaymentHelperTest.cls-meta.xml index 3a10d2e..f5e18fd 100644 --- a/force-app/main/default/classes/AdyenPaymentHelperTest.cls-meta.xml +++ b/force-app/main/default/classes/AdyenPaymentHelperTest.cls-meta.xml @@ -2,4 +2,4 @@ 60.0 Active - \ No newline at end of file + diff --git a/force-app/main/default/classes/AdyenPaymentUtility.cls b/force-app/main/default/classes/AdyenPaymentUtility.cls index ca0063e..0864bdc 100644 --- a/force-app/main/default/classes/AdyenPaymentUtility.cls +++ b/force-app/main/default/classes/AdyenPaymentUtility.cls @@ -7,20 +7,6 @@ public with sharing class AdyenPaymentUtility { private static final String NO_ADYEN_ADAPTER_BY_NAME = 'No Adyen adapter found with this name: '; @TestVisible private static final String NO_ADYEN_ADAPTER_BY_MERCHANT = 'No Adyen adapter found for this merchant account: '; - - /** - * Looks for the Gateway ref number on the Payment record passed in. If not found gets its from - * the LastPaymentGateway log on the OrderPaymentSummary record. - * - * @param payment the Payment sObject. - * @return the GatewayRefNumber for the request. - */ - public static String getRefundGatewayRefNumber(Payment payment) { - if (payment == null) { - throw new AdyenAsyncAdapter.GatewayException('Payment Info Missing'); - } - return payment.PaymentAuthorization?.GatewayRefNumber != null ? payment.PaymentAuthorization.GatewayRefNumber : payment.GatewayRefNumber; - } /** * Retrieve Payment Info. @@ -33,9 +19,8 @@ public with sharing class AdyenPaymentUtility { SELECT Id, GatewayRefNumber, GatewayRefDetails, PaymentAuthorization.GatewayRefNumber, PaymentAuthorization.Adyen_Payment_Method_Variant__c, - PaymentAuthorization.Adyen_Payment_Method__c, adyenOverrideMerchantConfig__c, - PaymentAuthorization.adyenOverrideMerchantConfig__c,CurrencyIsoCode, - OrderPaymentSummary.FullName, OrderPaymentSummary.OrderSummary.SalesChannel.AdyenMerchantID__c + PaymentAuthorization.Adyen_Payment_Method__c,CurrencyIsoCode, OrderPaymentSummary.FullName, + OrderPaymentSummary.OrderSummary.SalesChannel.AdyenMerchantID__c FROM Payment WHERE @@ -83,6 +68,14 @@ public with sharing class AdyenPaymentUtility { return adyenAdapters[0]; } + public static Adyen_Adapter__mdt chooseAdapterWithFallBack(String merchantAccountName) { + if (String.isNotBlank(merchantAccountName)) { + return retrieveAdapterByMerchantAcct(merchantAccountName); + } else { + return retrieveGatewayMetadata(AdyenConstants.DEFAULT_ADAPTER_NAME); + } + } + public static Boolean isValidNotification(NotificationRequestItem notificationRequestItem) { return AdyenOMSConstants.VALID_NOTIFICATION_TYPES.contains(notificationRequestItem.eventCode.toUpperCase()) && isValidPspReference(notificationRequestItem.originalReference) @@ -104,7 +97,7 @@ public with sharing class AdyenPaymentUtility { public static PaymentAuthorization retrievePaymentAuthorization(Id paymentAuthId) { List paymentAuthorizations = [ SELECT - Id, PaymentAuthorizationNumber, GatewayRefNumber, adyenOverrideMerchantConfig__c, Adyen_Payment_Method_Variant__c, + Id, PaymentAuthorizationNumber, GatewayRefNumber, Adyen_Payment_Method_Variant__c, OrderPaymentSummary.LastPaymentGatewayLog.GatewayRefNumber, OrderPaymentSummary.Id, OrderPaymentSummary.FullName, CurrencyIsoCode, @@ -130,7 +123,7 @@ public with sharing class AdyenPaymentUtility { public static Boolean checkIfOpenInvoiceFromAuthorization(PaymentAuthorization pa) { if (pa != null && pa.Adyen_Payment_Method_Variant__c != null) { for (String openInvoiceMethod : AdyenOMSConstants.OPEN_INVOICE_METHODS) { - if(pa.Adyen_Payment_Method_Variant__c.containsIgnoreCase(openInvoiceMethod)) { + if (pa.Adyen_Payment_Method_Variant__c.containsIgnoreCase(openInvoiceMethod)) { return true; } } @@ -173,20 +166,6 @@ public with sharing class AdyenPaymentUtility { } } - /** - * Looks for the Gateway ref number on the PaymentAuthorization record passed in. If not found gets its from - * the LastPaymentGateway log on the OrderPaymentSummary record. - * - * @param pa The PaymentAuthorization sObject - * @return the GatewayRefNumber for the request. - */ - public static String getCaptureGatewayRefNumber(PaymentAuthorization pa) { - if (pa == null) { - throw new AdyenAsyncAdapter.GatewayException('Payment Authorization Missing'); - } - return pa.GatewayRefNumber != null ? pa.GatewayRefNumber : pa.OrderPaymentSummary?.LastPaymentGatewayLog?.GatewayRefNumber; - } - public static List addInvoiceData(Id invoiceId) { List invoiceLines = [ SELECT Id, Product2.Name, Quantity, CurrencyIsoCode, ChargeAmount, ChargeTaxAmount, ChargeAmountWithTax, Type @@ -333,38 +312,33 @@ public with sharing class AdyenPaymentUtility { return info; } - - /** - * Create a modification request by populating required properties (capture/refund) - * - * @param paymentRequest capture or refund request - * @param currencyCode string containing the currency ISO code - * @param adyenAdapter custom metadata type containing the configuration - * @return CheckoutModificationRequest to send to Adyen. - */ - public static CheckoutModificationRequest createModificationRequest(CommercePayments.PaymentGatewayRequest paymentRequest, String currencyCode, Adyen_Adapter__mdt adyenAdapter) { + + public static CheckoutModificationRequest createModificationRequest(CommercePayments.PaymentGatewayRequest paymentRequest, String currencyIsoCode, Adyen_Adapter__mdt adyenAdapter) { CheckoutModificationRequest modRequest; Decimal price; String reference; + if (paymentRequest instanceof CommercePayments.CaptureRequest) { - modRequest = new CheckoutCaptureRequest(); CommercePayments.CaptureRequest captureRequest = (CommercePayments.CaptureRequest)paymentRequest; price = captureRequest.amount; - reference = AdyenPaymentUtility.getReference(captureRequest); + modRequest = new CheckoutCaptureRequest(); + reference = getReference(captureRequest); } else if (paymentRequest instanceof CommercePayments.ReferencedRefundRequest) { - modRequest = new CheckoutRefundRequest(); CommercePayments.ReferencedRefundRequest refundRequest = (CommercePayments.ReferencedRefundRequest)paymentRequest; + modRequest = new CheckoutRefundRequest(); price = refundRequest.amount; - reference = AdyenPaymentUtility.getRandomNumber(16); + reference = getRandomNumber(16); } + Amount requestAmount = new Amount(); - requestAmount.value = (price * AdyenPaymentUtility.getAmountMultiplier(currencyCode)).longValue(); - requestAmount.currency_x = currencyCode; + requestAmount.value = (price * getAmountMultiplier(currencyIsoCode)).longValue(); + requestAmount.currency_x = currencyIsoCode; modRequest.setAmount(requestAmount); modRequest.setReference(reference); modRequest.setMerchantAccount(adyenAdapter.Merchant_Account__c); - modRequest.setApplicationInfo(AdyenPaymentUtility.getApplicationInfo(adyenAdapter.System_Integrator_Name__c)); + modRequest.setApplicationInfo(getApplicationInfo(adyenAdapter.System_Integrator_Name__c)); + return modRequest; } diff --git a/force-app/main/default/classes/AdyenRefundHelper.cls b/force-app/main/default/classes/AdyenRefundHelper.cls index f958e9b..8419653 100644 --- a/force-app/main/default/classes/AdyenRefundHelper.cls +++ b/force-app/main/default/classes/AdyenRefundHelper.cls @@ -11,38 +11,35 @@ public with sharing class AdyenRefundHelper { * @see AdyenClient */ public static CommercePayments.GatewayResponse refund(CommercePayments.ReferencedRefundRequest refundRequest) { - // Retrieve the Payment Payment payment = AdyenPaymentUtility.retrievePayment(refundRequest.paymentId); String errorMessage = null; if (payment == null) { errorMessage = 'Payment Info Missing'; - } - if (refundRequest.amount == null) { + } else if (String.isBlank(payment.GatewayRefNumber)) { + errorMessage = 'PspReference Missing'; + } else if (refundRequest.amount == null) { errorMessage = 'Payment Amount Missing'; } if (errorMessage != null) { throw new AdyenAsyncAdapter.GatewayException(errorMessage); } - // By Default, retrieve the metadata key from the order's sales channel - String adapterName = payment?.OrderPaymentSummary.OrderSummary.SalesChannel.AdyenMerchantID__c; - // Override config for this specific Payment (i.e., a pre-capture) or inherit override from the original PaymentAuthorization - if (String.isNotBlank(payment?.adyenOverrideMerchantConfig__c)) { - adapterName = payment.adyenOverrideMerchantConfig__c; - } - if (String.isNotBlank(payment?.PaymentAuthorization?.adyenOverrideMerchantConfig__c)) { - adapterName = payment.PaymentAuthorization.adyenOverrideMerchantConfig__c; - } - if (String.isBlank(adapterName)) { - adapterName = AdyenConstants.DEFAULT_ADAPTER_NAME; - } - Adyen_Adapter__mdt adyenAdapterMdt = AdyenPaymentUtility.retrieveGatewayMetadata(adapterName); - String pspReference = AdyenPaymentUtility.getRefundGatewayRefNumber(payment); - CheckoutModificationRequest modRequest = AdyenPaymentUtility.createModificationRequest(refundRequest, payment.CurrencyIsoCode, adyenAdapterMdt); + + String pspReference = payment.PaymentAuthorization?.GatewayRefNumber != null ? payment.PaymentAuthorization.GatewayRefNumber : payment.GatewayRefNumber; + String merchantAccount = payment.OrderPaymentSummary.OrderSummary.SalesChannel.AdyenMerchantID__c; + Adyen_Adapter__mdt adyenAdapterMdt = AdyenPaymentUtility.chooseAdapterWithFallBack(merchantAccount); + + CheckoutRefundRequest modRequest = createRefundRequest(refundRequest, payment, adyenAdapterMdt); + CheckoutRefundResponse refundResponse = (CheckoutRefundResponse)AdyenPaymentUtility.sendModificationRequest(modRequest, adyenAdapterMdt, pspReference); + return processRefundResponse(refundResponse, refundRequest.amount); + } + + private static CheckoutRefundRequest createRefundRequest(CommercePayments.ReferencedRefundRequest refundRequest, Payment payment, Adyen_Adapter__mdt adyenAdapter) { + CheckoutRefundRequest modRequest = (CheckoutRefundRequest)AdyenPaymentUtility.createModificationRequest(refundRequest, payment.CurrencyIsoCode, adyenAdapter); //Only for Paypal Refunds - Capture reference must be a substring of refund reference if (String.isNotBlank(payment.PaymentAuthorization.Adyen_Payment_Method_Variant__c)) { if (payment.PaymentAuthorization.Adyen_Payment_Method_Variant__c.equalsIgnoreCase('Paypal') && String.isNotBlank(payment.GatewayRefDetails)) { - String refundReference = modRequest.getReference() + payment.GatewayRefDetails; //payment.GatewayRefDetails has the capture reference + String refundReference = modRequest.getReference() + payment.GatewayRefDetails; modRequest.setReference(refundReference); } } @@ -51,11 +48,10 @@ public with sharing class AdyenRefundHelper { modRequest.setLineItems(AdyenPaymentUtility.addCreditMemoData(payment.OrderPaymentSummary.OrderSummaryId)); } - CheckoutRefundResponse refundResponse = (CheckoutRefundResponse)AdyenPaymentUtility.sendModificationRequest(modRequest, adyenAdapterMdt, pspReference); - return processRefundResponse(refundResponse, refundRequest.amount); + return modRequest; } - public static CommercePayments.GatewayResponse processRefundResponse(CheckoutRefundResponse refundResponse, Decimal amount) { + private static CommercePayments.GatewayResponse processRefundResponse(CheckoutRefundResponse refundResponse, Decimal amount) { CommercePayments.ReferencedRefundResponse salesforceResponse = new CommercePayments.ReferencedRefundResponse(); salesforceResponse.setAsync(true); salesforceResponse.setAmount(Double.valueOf(amount)); @@ -67,4 +63,4 @@ public with sharing class AdyenRefundHelper { salesforceResponse.setGatewayMessage('[refund-received]'); return salesforceResponse; } -} \ No newline at end of file +} diff --git a/force-app/main/default/classes/TestDataFactory.cls-meta.xml b/force-app/main/default/classes/TestDataFactory.cls-meta.xml index 3a10d2e..f5e18fd 100644 --- a/force-app/main/default/classes/TestDataFactory.cls-meta.xml +++ b/force-app/main/default/classes/TestDataFactory.cls-meta.xml @@ -2,4 +2,4 @@ 60.0 Active - \ No newline at end of file + diff --git a/force-app/main/default/gatewayProviderPaymentMethodTypes/AlternativePaymentMethod.gatewayProviderPaymentMethodType-meta.xml b/force-app/main/default/gatewayProviderPaymentMethodTypes/AlternativePaymentMethod.gatewayProviderPaymentMethodType-meta.xml new file mode 100644 index 0000000..b6cd26e --- /dev/null +++ b/force-app/main/default/gatewayProviderPaymentMethodTypes/AlternativePaymentMethod.gatewayProviderPaymentMethodType-meta.xml @@ -0,0 +1,8 @@ + + + AdyenComponent + Alternative Payment Method + Adyen + AlternativePaymentMethod + AlternativePaymentMethod.Alternative_Payment_Method + diff --git a/force-app/main/default/layouts/Payment-Payment Layout.layout-meta.xml b/force-app/main/default/layouts/Payment-Payment Layout.layout-meta.xml deleted file mode 100644 index b97a761..0000000 --- a/force-app/main/default/layouts/Payment-Payment Layout.layout-meta.xml +++ /dev/null @@ -1,254 +0,0 @@ - - - - false - false - true - - - - Readonly - PaymentNumber - - - Edit - AccountId - - - Required - Amount - - - Edit - PaymentAuthorizationId - - - Edit - PaymentMethodId - - - Required - ProcessingMode - - - Readonly - Balance - - - Readonly - TotalRefundApplied - - - Edit - Date - - - Edit - Adyen_Payment_Method__c - - - Edit - Adyen_Payment_Method_Variant__c - - - Edit - adyenOverrideMerchantConfig__c - - - - - Required - Status - - - Required - Type - - - Required - CurrencyIsoCode - - - Edit - PaymentGroupId - - - Edit - EffectiveDate - - - Edit - Comments - - - Readonly - TotalRefundUnapplied - - - Readonly - NetRefundApplied - - - - - - false - true - true - - - - Readonly - ImpactAmount - - - Edit - CancellationDate - - - Edit - SfResultCode - - - Edit - CancellationSfResultCode - - - - - Edit - OrderPaymentSummaryId - - - Edit - CancellationEffectiveDate - - - - - - false - true - true - - - - Edit - PaymentGatewayId - - - Edit - GatewayResultCode - - - Edit - GatewayRefNumber - - - Edit - CancellationGatewayDate - - - Edit - CancellationGatewayRefNumber - - - - - Edit - GatewayDate - - - Edit - GatewayResultCodeDescription - - - Edit - GatewayRefDetails - - - Edit - CancellationGatewayResultCode - - - - - - false - true - true - - - - Readonly - CreatedById - - - Readonly - CreatedDate - - - Edit - MacAddress - - - Edit - Phone - - - - - Readonly - LastModifiedById - - - Readonly - LastModifiedDate - - - Edit - IpAddress - - - Edit - Email - - - - - - true - true - false - - - - - - - - Name - Refund - Amount - Type - Date - RefundLinesPayment - - - Name - Invoice - Amount - Type - Date - PaymentLinesInvoice - - false - false - false - - 00h8c00000hPHQ1 - 4 - 0 - Default - - diff --git a/force-app/main/default/layouts/PaymentAuthorization-Payment Authorization Layout.layout-meta.xml b/force-app/main/default/layouts/PaymentAuthorization-Payment Authorization Layout.layout-meta.xml deleted file mode 100644 index 9e8dd51..0000000 --- a/force-app/main/default/layouts/PaymentAuthorization-Payment Authorization Layout.layout-meta.xml +++ /dev/null @@ -1,193 +0,0 @@ - - - - false - false - true - - - - Readonly - PaymentAuthorizationNumber - - - Edit - AccountId - - - Required - Amount - - - Edit - PaymentMethodId - - - Edit - ExpirationDate - - - Required - ProcessingMode - - - Edit - Adyen_Payment_Method__c - - - Edit - Adyen_Payment_Method_Variant__c - - - Edit - adyenOverrideMerchantConfig__c - - - - - Required - Status - - - Edit - Date - - - Required - CurrencyIsoCode - - - Edit - PaymentGroupId - - - Edit - EffectiveDate - - - Edit - Comments - - - - - - false - true - true - - - - Edit - OrderPaymentSummaryId - - - Readonly - SfResultCode - - - - - - - false - true - true - - - - Edit - PaymentGatewayId - - - Edit - GatewayAuthCode - - - Edit - GatewayResultCode - - - Edit - GatewayRefNumber - - - - - Edit - GatewayDate - - - Edit - GatewayResultCodeDescription - - - Edit - GatewayRefDetails - - - - - - false - true - true - - - - Readonly - CreatedById - - - Readonly - CreatedDate - - - Edit - MacAddress - - - Edit - Phone - - - - - Readonly - LastModifiedById - - - Readonly - LastModifiedDate - - - Edit - IpAddress - - - Edit - Email - - - - - - true - true - false - - - - - - - false - false - false - - 00h8c00000hPHQ3 - 4 - 0 - Default - - diff --git a/force-app/main/default/layouts/SalesChannel-Sales Channel Layout.layout-meta.xml b/force-app/main/default/layouts/SalesChannel-Sales Channel Layout.layout-meta.xml deleted file mode 100644 index 7899b5d..0000000 --- a/force-app/main/default/layouts/SalesChannel-Sales Channel Layout.layout-meta.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - true - true - true - - - - Required - SalesChannelName - - - Edit - AdyenMerchantID__c - - - Edit - Description - - - Edit - ExternalChannelNumber - - - - - - true - true - false - - - - - - - - ORDERS.ORDER_NUMBER - Orders - - false - false - false - - 00h8c00000hPHPt - 4 - 0 - Default - - diff --git a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Endpoint_Api_Version__c.field-meta.xml b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Endpoint_Api_Version__c.field-meta.xml index f9f2b7b..1cc6afe 100644 --- a/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Endpoint_Api_Version__c.field-meta.xml +++ b/force-app/main/default/objects/Adyen_Adapter__mdt/fields/Endpoint_Api_Version__c.field-meta.xml @@ -1,10 +1,10 @@ Endpoint_Api_Version__c - The Api Version of the Endpoint, e.g. v52 + The Api Version of the Endpoint, e.g. v71 false DeveloperControlled - The Api Version of the Endpoint, e.g. v52 + The Api Version of the Endpoint, e.g. v71 16 false diff --git a/force-app/main/default/objects/AlternativePaymentMethod/recordTypes/Alternative_Payment_Method.recordType-meta.xml b/force-app/main/default/objects/AlternativePaymentMethod/recordTypes/Alternative_Payment_Method.recordType-meta.xml new file mode 100644 index 0000000..362b747 --- /dev/null +++ b/force-app/main/default/objects/AlternativePaymentMethod/recordTypes/Alternative_Payment_Method.recordType-meta.xml @@ -0,0 +1,6 @@ + + + Alternative_Payment_Method + true + + diff --git a/force-app/main/default/objects/Payment/fields/adyenOverrideMerchantConfig__c.field-meta.xml b/force-app/main/default/objects/Payment/fields/adyenOverrideMerchantConfig__c.field-meta.xml deleted file mode 100644 index afbd6cc..0000000 --- a/force-app/main/default/objects/Payment/fields/adyenOverrideMerchantConfig__c.field-meta.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - adyenOverrideMerchantConfig__c - false - - 40 - false - Text - false - diff --git a/force-app/main/default/objects/PaymentAuthorization/fields/adyenOverrideMerchantConfig__c.field-meta.xml b/force-app/main/default/objects/PaymentAuthorization/fields/adyenOverrideMerchantConfig__c.field-meta.xml deleted file mode 100644 index afbd6cc..0000000 --- a/force-app/main/default/objects/PaymentAuthorization/fields/adyenOverrideMerchantConfig__c.field-meta.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - adyenOverrideMerchantConfig__c - false - - 40 - false - Text - false - diff --git a/force-app/main/default/objects/SalesChannel/fields/AdyenMerchantID__c.field-meta.xml b/force-app/main/default/objects/SalesChannel/fields/AdyenMerchantID__c.field-meta.xml index 5b11ec0..c35b047 100644 --- a/force-app/main/default/objects/SalesChannel/fields/AdyenMerchantID__c.field-meta.xml +++ b/force-app/main/default/objects/SalesChannel/fields/AdyenMerchantID__c.field-meta.xml @@ -3,7 +3,7 @@ AdyenMerchantID__c false false - Must match the custom metadata record name. The actual Adyen merchant ID is pulled from metadata config + The merchant account name for this Sales Channel. If left blank, the Adyen Default custom metadata record is used. 50 false diff --git a/force-app/main/default/paymentGatewayProviders/Adyen.paymentGatewayProvider-meta.xml b/force-app/main/default/paymentGatewayProviders/Adyen.paymentGatewayProvider-meta.xml new file mode 100644 index 0000000..fa2a9ad --- /dev/null +++ b/force-app/main/default/paymentGatewayProviders/Adyen.paymentGatewayProvider-meta.xml @@ -0,0 +1,6 @@ + + + AdyenAsyncAdapter + Yes + SalesforceOrderManagement-Adyen + diff --git a/force-app/main/default/profiles/Admin.profile-meta.xml b/force-app/main/default/profiles/Admin.profile-meta.xml index 7e9edf3..7ce5dbf 100644 --- a/force-app/main/default/profiles/Admin.profile-meta.xml +++ b/force-app/main/default/profiles/Admin.profile-meta.xml @@ -1,5 +1,53 @@ + + AdyenAsyncAdapter + true + + + AdyenAsyncAdapterTest + true + + + AdyenAuthorisationHelper + true + + + AdyenAuthorisationHelperTest + true + + + AdyenCaptureHelper + true + + + AdyenOMSConstants + true + + + AdyenPaymentHelper + true + + + AdyenPaymentHelperTest + true + + + AdyenPaymentUtility + true + + + AdyenPaymentUtilityTest + true + + + AdyenRefundHelper + true + + + TestDataFactory + true + false true @@ -11,11 +59,6 @@ Payment.Adyen_Payment_Method__c true - - true - Payment.adyenOverrideMerchantConfig__c - true - true PaymentAuthorization.Adyen_Payment_Method_Variant__c @@ -28,762 +71,42 @@ true - PaymentAuthorization.adyenOverrideMerchantConfig__c + SalesChannel.AdyenMerchantID__c true - Payment-Payment Layout + Adyen_Adapter__mdt-Adyen Adapter Layout - - PaymentAuthorization-Payment Authorization Layout - - Salesforce - - true - AIViewInsightObjects - - - true - ActivateContract - - - true - ActivateOrder - - - true - ActivitiesAccess - - - true - AddDirectMessageMembers - - - true - AllowUniversalSearch - - - true - AllowViewKnowledge - - - true - ApexRestServices - - - true - ApiEnabled - - - true - AssignPermissionSets - - - true - AssignTopics - - - true - AuthorApex - - - true - BulkMacrosAllowed - - - true - CanAccessCE - - - true - CanInsertFeedSystemFields - - - true - CanUseNewDashboardBuilder - - - true - CanVerifyComment - - - true - ChangeDashboardColors - - - true - ChatterEditOwnPost - - - true - ChatterEditOwnRecordPost - - - true - ChatterFileLink - - - true - ChatterInternalUser - - - true - ChatterInviteExternalUsers - - - true - ChatterOwnGroups - - - true - ClientSecretRotation - - - true - ConnectOrgToEnvironmentHub - - - true - ConsentApiUpdate - - - true - ContentAdministrator - - - true - ContentWorkspaces - - - true - ConvertLeads - - - true - CreateCustomizeDashboards - - - true - CreateCustomizeFilters - - - true - CreateCustomizeReports - - - true - CreateDashboardFolders - - - true - CreateLtngTempFolder - - - true - CreatePackaging - - - true - CreateReportFolders - - - true - CreateTopics - - - true - CreateWorkspaces - - - true - CustomizeApplication - - - true - DataExport - - - true - DelegatedTwoFactor - - - true - DeleteActivatedContract - - - true - DeleteTopics - - - true - DistributeFromPersWksp - - - true - EditActivatedOrders - - - true - EditBillingInfo - - - true - EditBrandTemplates - - - true - EditCaseComments - - - true - EditDeliveryInformation - - - true - EditEvent - - - true - EditHtmlTemplates - - - true - EditKnowledge - - - true - EditMyDashboards - - - true - EditMyReports - - - true - EditOppLineItemUnitPrice - - - true - EditPublicDocuments - - - true - EditPublicFilters - - - true - EditPublicTemplates - - - true - EditReadonlyFields - - - true - EditTask - - - true - EditTopics - - - true - EmailMass - - - true - EmailSingle - - - true - EnableCommunityAppLauncher - - - true - EnableNotifications - - - true - ExportReport - - - true - GiveRecognitionBadge - - - true - ImportCustomObjects - - - true - ImportLeads - - - true - ImportPersonal - - - true - InstallPackaging - - - true - LightningConsoleAllowedForUser - - - true - LightningExperienceUser - - - true - ListEmailSend - - - true - ManageAnalyticSnapshots - - - true - ManageAuthProviders - - - true - ManageBusinessHourHolidays - - - true - ManageC360AConnections - - - true - ManageCMS - - - true - ManageCallCenters - - - true - ManageCases - - - true - ManageCategories - - - true - ManageCertificates - - - true - ManageContentPermissions - - - true - ManageContentProperties - - - true - ManageContentTypes - - - true - ManageCustomPermissions - - - true - ManageCustomReportTypes - - - true - ManageDashbdsInPubFolders - - - true - ManageDataCategories - - - true - ManageDataIntegrations - - - true - ManageDynamicDashboards - - - true - ManageEmailClientConfig - - - true - ManageEntitlements - - - true - ManageExchangeConfig - - - true - ManageHealthCheck - - - true - ManageHubConnections - - - true - ManageInteraction - - - true - ManageInternalUsers - - - true - ManageIpAddresses - - - true - ManageKnowledge - - - true - ManageKnowledgeImportExport - - - true - ManageLeads - - - true - ManageLoginAccessPolicies - - - true - ManageMobile - - - true - ManageNetworks - - - true - ManageOrchInstsAndWorkItems - - - true - ManagePackageLicenses - - - true - ManagePasswordPolicies - - - true - ManageProfilesPermissionsets - - - true - ManagePropositions - - - true - ManagePvtRptsAndDashbds - - - true - ManageRecommendationStrategies - - - true - ManageReleaseUpdates - - - true - ManageRemoteAccess - - - true - ManageReportsInPubFolders - - - true - ManageRoles - - - true - ManageSearchPromotionRules - - - true - ManageSharing - - - true - ManageSolutions - - - true - ManageSubscriptions - - - true - ManageSynonyms - - - true - ManageUnlistedGroups - - - true - ManageUsers - - - true - MassInlineEdit - - - true - MergeTopics - - - true - ModerateChatter - - - true - ModifyAllData - - - true - ModifyDataClassification - - - true - ModifyMetadata - - - true - NewReportBuilder - - - true - OmnichannelInventorySync - - - true - Packaging2 - - - true - Packaging2Delete - - - true - PrivacyDataAccess - - - true - PublishPackaging - - - true - RemoveDirectMessageMembers - - - true - ResetPasswords - - - true - RunReports - - - true - ScheduleReports - - - true - SelectFilesFromSalesforce - - - true - SendCustomNotifications - - - true - SendSitRequests - - - true - ShareInternalArticles - - - true - ShowCompanyNameAsUserBadge - - - true - SolutionImport - - - true - SubmitMacrosAllowed - - - true - SubscribeDashboardRolesGrps - - - true - SubscribeDashboardToOtherUsers - - - true - SubscribeReportRolesGrps - - - true - SubscribeReportToOtherUsers - - - true - SubscribeReportsRunAsUser - - - true - SubscribeToLightningDashboards - - - true - SubscribeToLightningReports - - - true - TransactionalEmailSend - - - true - TransferAnyCase - - - true - TransferAnyEntity - - - true - TransferAnyLead - - - true - UseFulfillmentAPIs - - - true - UseOmnichannelInventoryAPIs - - - true - UseOrderManagementAPIs - - - true - UseReturnOrder - - - true - UseReturnOrderAPIs - - - true - UseShipment - - - true - UseTeamReassignWizards - - - true - UseWebLink - - - true - ViewAllData - - - true - ViewAllProfiles - - - true - ViewAllUsers - - - true - ViewDataAssessment - - - true - ViewDataCategories - - - true - ViewDataLeakageEvents - - - true - ViewDeveloperName - - - true - ViewEventLogFiles - - - true - ViewFlowUsageAndFlowEventData - - - true - ViewHealthCheck - - - true - ViewHelpLink - - - true - ViewMLModels - - - true - ViewMyTeamsDashboards - - - true - ViewPlatformEvents - - - true - ViewPublicDashboards - - - true - ViewPublicReports - - - true - ViewRoles - - - true - ViewSetup - - - true - ViewUserPII - - - true - WorkCalibrationUser - + + true + true + true + true + true + AlternativePaymentMethod + true + + + true + true + true + true + true + Payment + true + + + true + true + true + true + true + PaymentAuthorization + true + + + true + AlternativePaymentMethod.Alternative_Payment_Method + true + diff --git a/manifest/package.xml b/manifest/package.xml new file mode 100644 index 0000000..5550f09 --- /dev/null +++ b/manifest/package.xml @@ -0,0 +1,74 @@ + + + + AdyenAsyncAdapter + AdyenAsyncAdapterTest + AdyenAuthorisationHelper + AdyenAuthorisationHelperTest + AdyenCaptureHelper + AdyenOMSConstants + AdyenPaymentHelper + AdyenPaymentHelperTest + AdyenPaymentUtility + AdyenPaymentUtilityTest + AdyenRefundHelper + TestDataFactory + ApexClass + + + Adyen_Adapter.AdyenDefault + CustomMetadata + + + Adyen_Adapter__mdt + AlternativePaymentMethod + Payment + PaymentAuthorization + CustomObject + + + SalesChannel.AdyenMerchantID__c + Adyen_Adapter__mdt.Authorize_Endpoint__c + Adyen_Adapter__mdt.Capture_Endpoint__c + Adyen_Adapter__mdt.Endpoint_Api_Version__c + Adyen_Adapter__mdt.Endpoint_Path__c + Adyen_Adapter__mdt.HMAC_Key__c + Adyen_Adapter__mdt.Merchant_Account__c + Adyen_Adapter__mdt.Refund_Endpoint__c + Adyen_Adapter__mdt.System_Integrator_Name__c + Payment.Adyen_Payment_Method__c + Payment.Adyen_Payment_Method_Variant__c + PaymentAuthorization.Adyen_Payment_Method__c + PaymentAuthorization.Adyen_Payment_Method_Variant__c + CustomField + + + Adyen_Adapter__mdt-Adyen Adapter Layout + Layout + + + AdyenCheckout + NamedCredential + + + Admin + Profile + + + * + ExternalCredential + + + * + PaymentGatewayProvider + + + AlternativePaymentMethod + GatewayProviderPaymentMethodType + + + AlternativePaymentMethod.Alternative_Payment_Method + RecordType + + 60.0 + From 64225f037675db9fdb410f93ec9f0337eba95816 Mon Sep 17 00:00:00 2001 From: daniloc Date: Thu, 25 Jul 2024 10:29:48 +0200 Subject: [PATCH 13/21] feat: release 3.0.0 --- force-app/main/default/classes/AdyenOMSConstants.cls | 4 ++-- .../Adyen_Adapter.AdyenDefault.md-meta.xml | 2 +- sfdx-project.json | 11 ++++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/force-app/main/default/classes/AdyenOMSConstants.cls b/force-app/main/default/classes/AdyenOMSConstants.cls index c1f0351..0a5928f 100644 --- a/force-app/main/default/classes/AdyenOMSConstants.cls +++ b/force-app/main/default/classes/AdyenOMSConstants.cls @@ -8,9 +8,9 @@ public with sharing class AdyenOMSConstants { public static final String EXTERNAL_PLATFORM_NAME_FOR_APP_INFO = 'Adyen Salesforce OMS'; public static final String ADYEN_LIBRARY_NAME_FOR_APP_INFO = 'adyen-apex-api-library'; - public static final String ADYEN_LIBRARY_VERSION_FOR_APP_INFO = '3.0.1'; + public static final String ADYEN_LIBRARY_VERSION_FOR_APP_INFO = '3.2.0'; public static final String MERCHANT_APP_NAME_FOR_APP_INFO = 'adyen-salesforce-oms'; - public static final String MERCHANT_APP_VERSION_FOR_APP_INFO = '2.1.2'; + public static final String MERCHANT_APP_VERSION_FOR_APP_INFO = '3.0.0'; public static final String CARD_PAYMENT_METHOD_OBJECT = 'CardPaymentMethod'; public static final String ALTERNATIVE_PAYMENT_METHOD_OBJECT = 'AlternativePaymentMethod'; diff --git a/force-app/main/default/customMetadata/Adyen_Adapter.AdyenDefault.md-meta.xml b/force-app/main/default/customMetadata/Adyen_Adapter.AdyenDefault.md-meta.xml index dd0b968..4c2deef 100644 --- a/force-app/main/default/customMetadata/Adyen_Adapter.AdyenDefault.md-meta.xml +++ b/force-app/main/default/customMetadata/Adyen_Adapter.AdyenDefault.md-meta.xml @@ -12,7 +12,7 @@ Endpoint_Api_Version__c - /v70 + /v71 Endpoint_Path__c diff --git a/sfdx-project.json b/sfdx-project.json index 4a0a866..49027da 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -5,12 +5,12 @@ "default": true, "package": "Adyen Salesforce Order Management", "definitionFile": "config/project-scratch-def.json", - "versionName": "version 2.1", - "versionNumber": "2.1.2.NEXT", + "versionName": "version 3.0", + "versionNumber": "3.0.0.NEXT", "ancestorVersion": "HIGHEST", "dependencies": [ { - "package": "API Library Apex Adyen@3.0.1-1" + "package": "API Library Apex Adyen@3.2.0-2" } ] } @@ -20,10 +20,11 @@ "sourceApiVersion": "60.0", "packageAliases": { "Adyen Salesforce Order Management": "0Ho4T000000blPMSAY", - "API Library Apex Adyen@3.0.1-1": "04tRP0000000A5pYAE", + "API Library Apex Adyen@3.2.0-2": "04tRP0000000j5BYAQ", "Adyen Salesforce Order Management@2.0.0": "04t4T000001y7C0QAI", "Adyen Salesforce Order Management@2.1.0": "04tRP00000002BVYAY", "Adyen Salesforce Order Management@2.1.1-1": "04tRP0000000A7RYAU", - "Adyen Salesforce Order Management@2.1.2-1": "04tRP0000000AFVYA2" + "Adyen Salesforce Order Management@2.1.2-1": "04tRP0000000AFVYA2", + "Adyen Salesforce Order Management@3.0.0-1": "04tRP0000000lF3YAI" } } \ No newline at end of file From 9309c9c670b779110f698a838accefb0a76b94b4 Mon Sep 17 00:00:00 2001 From: Danilo Cardoso Date: Tue, 30 Jul 2024 15:49:00 +0200 Subject: [PATCH 14/21] task: changing names to avoid conflict (#43) Co-authored-by: daniloc --- .../default/classes/AdyenAsyncAdapter.cls | 30 +--- .../classes/AdyenAuthorisationHelperTest.cls | 2 +- .../default/classes/AdyenCaptureHelper.cls | 2 +- .../default/classes/AdyenGatewayAdapter.cls | 18 +++ .../classes/AdyenGatewayAdapter.cls-meta.xml | 5 + .../classes/AdyenGatewayAdapterTest.cls | 131 ++++++++++++++++++ .../AdyenGatewayAdapterTest.cls-meta.xml | 5 + .../default/classes/AdyenPaymentHelper.cls | 2 +- .../classes/AdyenPaymentHelperTest.cls | 2 +- .../default/classes/AdyenPaymentUtility.cls | 10 +- .../default/classes/AdyenRefundHelper.cls | 3 +- .../main/default/classes/TestDataFactory.cls | 2 +- ....gatewayProviderPaymentMethodType-meta.xml | 2 +- ..._Provider.paymentGatewayProvider-meta.xml} | 4 +- manifest/package.xml | 2 + 15 files changed, 178 insertions(+), 42 deletions(-) create mode 100644 force-app/main/default/classes/AdyenGatewayAdapter.cls create mode 100644 force-app/main/default/classes/AdyenGatewayAdapter.cls-meta.xml create mode 100644 force-app/main/default/classes/AdyenGatewayAdapterTest.cls create mode 100644 force-app/main/default/classes/AdyenGatewayAdapterTest.cls-meta.xml rename force-app/main/default/paymentGatewayProviders/{Adyen.paymentGatewayProvider-meta.xml => Adyen_OMS_Provider.paymentGatewayProvider-meta.xml} (63%) diff --git a/force-app/main/default/classes/AdyenAsyncAdapter.cls b/force-app/main/default/classes/AdyenAsyncAdapter.cls index e82068b..3f94429 100644 --- a/force-app/main/default/classes/AdyenAsyncAdapter.cls +++ b/force-app/main/default/classes/AdyenAsyncAdapter.cls @@ -1,30 +1,12 @@ /** - * This adapter is called by the Payment Gateway. - * The http calls are delegated to the AdyenPaymentHelper Class. - * - * This will process a CAPTURE and a REFUND Request as well as the corresponding Async callbacks. - * - * @see AdyenPaymentHelper - * @see AdyenClient + * This class is being deprecated after v2 due to the introduction of the payment gateway provider metadata. + * This way when upgrading the package conflicts will be avoided. + * The new one should be AdyenGatewayAdapter. */ global with sharing class AdyenAsyncAdapter implements CommercePayments.PaymentGatewayAdapter, CommercePayments.PaymentGatewayAsyncAdapter { global AdyenAsyncAdapter() {} - /** - * The entry point for processing payment requests. Returns the response from the payment gateway. - * Accepts the gateway context request and handover the operation to AdyenPaymentHelper to call the appropriate capture or refund operation. - * - * @param paymentGatewayContext from SF Commerce Payments - * @return CommercePayments.GatewayResponse - * - * @implNotes - * [CAPTURE] is called after setting Fulfillment.Status to 'Fulfilled' which in turns fires processes - * and flows to create invoices which ultimately fires this. - * - * [REFUND] is called by using the action on the order summary page (left hand side). - * - */ global CommercePayments.GatewayResponse processRequest(CommercePayments.PaymentGatewayContext paymentGatewayContext) { try { return AdyenPaymentHelper.handleFulfillmentOrderStatusChange(paymentGatewayContext); @@ -33,12 +15,6 @@ global with sharing class AdyenAsyncAdapter implements CommercePayments.PaymentG } } - /** - * Listens to the incoming async notification callback from Adyen and handover to AdyenPaymentHelper for processing - * - * @param gatewayNotificationContext from SF Commerce Payments - * @return CommercePayments.GatewayNotificationResponse - */ global CommercePayments.GatewayNotificationResponse processNotification(CommercePayments.PaymentGatewayNotificationContext gatewayNotificationContext) { return AdyenPaymentHelper.handleAsyncNotificationCallback(gatewayNotificationContext); } diff --git a/force-app/main/default/classes/AdyenAuthorisationHelperTest.cls b/force-app/main/default/classes/AdyenAuthorisationHelperTest.cls index 6bc2573..90ced69 100644 --- a/force-app/main/default/classes/AdyenAuthorisationHelperTest.cls +++ b/force-app/main/default/classes/AdyenAuthorisationHelperTest.cls @@ -90,7 +90,7 @@ private class AdyenAuthorisationHelperTest { AdyenAuthorisationHelper.authorise(TestDataFactory.createAuthorisationRequest(null)); Assert.fail(); } catch (Exception ex) { - Assert.isInstanceOfType(ex, AdyenAsyncAdapter.GatewayException.class); + Assert.isInstanceOfType(ex, AdyenGatewayAdapter.GatewayException.class); Assert.isTrue(ex.getMessage().containsIgnoreCase('400')); } Test.stopTest(); diff --git a/force-app/main/default/classes/AdyenCaptureHelper.cls b/force-app/main/default/classes/AdyenCaptureHelper.cls index 7b84443..73340d2 100644 --- a/force-app/main/default/classes/AdyenCaptureHelper.cls +++ b/force-app/main/default/classes/AdyenCaptureHelper.cls @@ -17,7 +17,7 @@ public with sharing class AdyenCaptureHelper { errorMessage = 'Payment Amount Missing'; } if (String.isNotBlank(errorMessage)) { - throw new AdyenAsyncAdapter.GatewayException(errorMessage); + throw new AdyenGatewayAdapter.GatewayException(errorMessage); } String pspReference = paymentAuth.GatewayRefNumber; diff --git a/force-app/main/default/classes/AdyenGatewayAdapter.cls b/force-app/main/default/classes/AdyenGatewayAdapter.cls new file mode 100644 index 0000000..a96b119 --- /dev/null +++ b/force-app/main/default/classes/AdyenGatewayAdapter.cls @@ -0,0 +1,18 @@ +global with sharing class AdyenGatewayAdapter implements CommercePayments.PaymentGatewayAdapter, CommercePayments.PaymentGatewayAsyncAdapter { + + global AdyenGatewayAdapter() {} + + global CommercePayments.GatewayResponse processRequest(CommercePayments.PaymentGatewayContext paymentGatewayContext) { + try { + return AdyenPaymentHelper.handleFulfillmentOrderStatusChange(paymentGatewayContext); + } catch (Exception ex) { + return new CommercePayments.GatewayErrorResponse('500', ex.getMessage()); + } + } + + global CommercePayments.GatewayNotificationResponse processNotification(CommercePayments.PaymentGatewayNotificationContext gatewayNotificationContext) { + return AdyenPaymentHelper.handleAsyncNotificationCallback(gatewayNotificationContext); + } + + public class GatewayException extends Exception {} +} \ No newline at end of file diff --git a/force-app/main/default/classes/AdyenGatewayAdapter.cls-meta.xml b/force-app/main/default/classes/AdyenGatewayAdapter.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/force-app/main/default/classes/AdyenGatewayAdapter.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/force-app/main/default/classes/AdyenGatewayAdapterTest.cls b/force-app/main/default/classes/AdyenGatewayAdapterTest.cls new file mode 100644 index 0000000..952a23e --- /dev/null +++ b/force-app/main/default/classes/AdyenGatewayAdapterTest.cls @@ -0,0 +1,131 @@ +@IsTest +private class AdyenGatewayAdapterTest { + @TestSetup + static void makeData() { + Account acct = TestDataFactory.createAccount(); + insert acct; + TestDataFactory.insertBasicPaymentRecords(acct.Id, null); + } + + @IsTest + static void testCaptureOutboundSuccess() { + Test.setMock(HttpCalloutMock.class, new TestDataFactory.EchoHttpMock()); + + Test.startTest(); + Id authId = [SELECT Id FROM PaymentAuthorization ORDER BY CreatedDate DESC LIMIT 1].Id; + CommercePayments.CaptureRequest captureRequest = new CommercePayments.CaptureRequest(TestDataFactory.TEST_AMOUNT, authId); + CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(captureRequest, CommercePayments.RequestType.Capture); + CommercePayments.GatewayResponse captureResponse = TestDataFactory.adyenAdapter.processRequest(context); + Test.stopTest(); + + Assert.isTrue(captureResponse.toString().contains('[capture-received]')); + Assert.isTrue(captureResponse.toString().contains(TestDataFactory.TEST_PSP_REFERENCE)); + } + + @IsTest + static void testCaptureOutboundFailure() { + Test.setMock(HttpCalloutMock.class, new TestDataFactory.FailureResponse()); + + Test.startTest(); + Id authId = [SELECT Id FROM PaymentAuthorization ORDER BY CreatedDate DESC LIMIT 1].Id; + CommercePayments.CaptureRequest captureRequest = new CommercePayments.CaptureRequest(TestDataFactory.TEST_AMOUNT, authId); + CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(captureRequest, CommercePayments.RequestType.Capture); + CommercePayments.GatewayResponse gatewayResponse = TestDataFactory.adyenAdapter.processRequest(context); + Test.stopTest(); + + Assert.isInstanceOfType(gatewayResponse, CommercePayments.GatewayErrorResponse.class); + Assert.isTrue(gatewayResponse.toString().containsIgnoreCase('400')); + } + + @IsTest + static void testCaptureOutboundMissingPaymentAuthorization() { + Test.setMock(HttpCalloutMock.class, new TestDataFactory.EchoHttpMock()); + + Test.startTest(); + CommercePayments.CaptureRequest captureRequest = new CommercePayments.CaptureRequest(TestDataFactory.TEST_AMOUNT, null); + CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(captureRequest, CommercePayments.RequestType.Capture); + CommercePayments.GatewayResponse gatewayResponse = TestDataFactory.adyenAdapter.processRequest(context); + Test.stopTest(); + + Assert.isInstanceOfType(gatewayResponse, CommercePayments.GatewayErrorResponse.class); + Assert.isTrue(gatewayResponse.toString().containsIgnoreCase(AdyenPaymentUtility.NO_PAYMENT_AUTH_FOUND_BY_ID)); + } + + @IsTest + static void testCaptureOutboundMissingAmount() { + Test.setMock(HttpCalloutMock.class, new TestDataFactory.EchoHttpMock()); + Id authId = [SELECT Id FROM PaymentAuthorization ORDER BY CreatedDate DESC LIMIT 1].Id; + Test.startTest(); + CommercePayments.CaptureRequest captureRequest = new CommercePayments.CaptureRequest(null, authId); + CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(captureRequest, CommercePayments.RequestType.Capture); + CommercePayments.GatewayResponse gatewayResponse = TestDataFactory.adyenAdapter.processRequest(context); + Test.stopTest(); + Assert.isInstanceOfType(gatewayResponse, CommercePayments.GatewayErrorResponse.class); + Assert.isTrue(gatewayResponse.toString().containsIgnoreCase('Payment Amount Missing')); + } + + @IsTest + static void testCaptureInboundSuccess() { + AdyenPaymentHelper.TEST_NOTIFICATION_REQUEST_BODY = TestDataFactory.createNotificationRequestBody('CAPTURE', TestDataFactory.TEST_PSP_REFERENCE); + + Test.startTest(); + CommercePayments.GatewayNotificationResponse captureResponse = TestDataFactory.adyenAdapter.processNotification(null); + Test.stopTest(); + + Assert.isFalse(captureResponse.toString().containsIgnoreCase('error')); + } + + @IsTest + static void testRefundOutboundSuccess() { + Test.setMock(HttpCalloutMock.class, new TestDataFactory.EchoHttpMock()); + + Test.startTest(); + Id paymentId = [SELECT Id FROM Payment ORDER BY CreatedDate DESC LIMIT 1].Id; + CommercePayments.ReferencedRefundRequest refundRequest = new CommercePayments.ReferencedRefundRequest(TestDataFactory.TEST_AMOUNT, paymentId); + CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(refundRequest, CommercePayments.RequestType.ReferencedRefund); + CommercePayments.GatewayResponse refundResponse = TestDataFactory.adyenAdapter.processRequest(context); + Test.stopTest(); + + Assert.isTrue(refundResponse.toString().contains('received')); + } + + @IsTest + static void testRefundOutboundFailure() { + Test.setMock(HttpCalloutMock.class, new TestDataFactory.FailureResponse()); + + Test.startTest(); + Id paymentId = [SELECT Id FROM Payment ORDER BY CreatedDate DESC LIMIT 1].Id; + CommercePayments.ReferencedRefundRequest refundRequest = new CommercePayments.ReferencedRefundRequest(TestDataFactory.TEST_AMOUNT, paymentId); + CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(refundRequest, CommercePayments.RequestType.ReferencedRefund); + CommercePayments.GatewayResponse refundResponse = TestDataFactory.adyenAdapter.processRequest(context); + Test.stopTest(); + + Assert.isInstanceOfType(refundResponse, CommercePayments.GatewayErrorResponse.class); + Assert.isTrue(refundResponse.toString().containsIgnoreCase('400')); + } + + @IsTest + static void testRefundOutboundMissingPayment() { + Test.setMock(HttpCalloutMock.class, new TestDataFactory.EchoHttpMock()); + Test.startTest(); + CommercePayments.ReferencedRefundRequest refundRequest = new CommercePayments.ReferencedRefundRequest(TestDataFactory.TEST_AMOUNT, null); + CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(refundRequest, CommercePayments.RequestType.ReferencedRefund); + CommercePayments.GatewayResponse gatewayResponse = TestDataFactory.adyenAdapter.processRequest(context); + Test.stopTest(); + Assert.isInstanceOfType(gatewayResponse, CommercePayments.GatewayErrorResponse.class); + Assert.isTrue(gatewayResponse.toString().containsIgnoreCase(AdyenPaymentUtility.NO_PAYMENT_FOUND_BY_ID)); + } + + @IsTest + static void testRefundOutboundMissingAmount() { + Test.setMock(HttpCalloutMock.class, new TestDataFactory.EchoHttpMock()); + Id paymentId = [SELECT Id FROM Payment ORDER BY CreatedDate DESC LIMIT 1].Id; + Test.startTest(); + CommercePayments.ReferencedRefundRequest refundRequest = new CommercePayments.ReferencedRefundRequest(null, paymentId); + CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(refundRequest, CommercePayments.RequestType.ReferencedRefund); + CommercePayments.GatewayResponse gatewayResponse = TestDataFactory.adyenAdapter.processRequest(context); + Test.stopTest(); + Assert.isInstanceOfType(gatewayResponse, CommercePayments.GatewayErrorResponse.class); + Assert.isTrue(gatewayResponse.toString().containsIgnoreCase('Payment Amount Missing')); + } +} \ No newline at end of file diff --git a/force-app/main/default/classes/AdyenGatewayAdapterTest.cls-meta.xml b/force-app/main/default/classes/AdyenGatewayAdapterTest.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/force-app/main/default/classes/AdyenGatewayAdapterTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/force-app/main/default/classes/AdyenPaymentHelper.cls b/force-app/main/default/classes/AdyenPaymentHelper.cls index c38787f..92a577a 100644 --- a/force-app/main/default/classes/AdyenPaymentHelper.cls +++ b/force-app/main/default/classes/AdyenPaymentHelper.cls @@ -107,7 +107,7 @@ public with sharing class AdyenPaymentHelper { notification = new CommercePayments.ReferencedRefundNotification(); gatewayMessageTemplate = '[refund-{0}] {1}'; } else { - throw new AdyenAsyncAdapter.GatewayException('Notification of type ' + notificationRequestItem.eventCode + ' does not match criteria'); + throw new AdyenGatewayAdapter.GatewayException('Notification of type ' + notificationRequestItem.eventCode + ' does not match criteria'); } String result; diff --git a/force-app/main/default/classes/AdyenPaymentHelperTest.cls b/force-app/main/default/classes/AdyenPaymentHelperTest.cls index f5fb43d..502dfcd 100644 --- a/force-app/main/default/classes/AdyenPaymentHelperTest.cls +++ b/force-app/main/default/classes/AdyenPaymentHelperTest.cls @@ -61,7 +61,7 @@ private class AdyenPaymentHelperTest { AdyenPaymentHelper.createNotificationSaveResult(nri); Assert.fail(); } catch (Exception ex) { // then - Assert.isInstanceOfType(ex, AdyenAsyncAdapter.GatewayException.class); + Assert.isInstanceOfType(ex, AdyenGatewayAdapter.GatewayException.class); } } } diff --git a/force-app/main/default/classes/AdyenPaymentUtility.cls b/force-app/main/default/classes/AdyenPaymentUtility.cls index 0864bdc..6bce282 100644 --- a/force-app/main/default/classes/AdyenPaymentUtility.cls +++ b/force-app/main/default/classes/AdyenPaymentUtility.cls @@ -27,7 +27,7 @@ public with sharing class AdyenPaymentUtility { Id = :paymentId ]; if (payments.isEmpty()) { - throw new AdyenAsyncAdapter.GatewayException(NO_PAYMENT_FOUND_BY_ID + paymentId); + throw new AdyenGatewayAdapter.GatewayException(NO_PAYMENT_FOUND_BY_ID + paymentId); } return payments[0]; } @@ -48,7 +48,7 @@ public with sharing class AdyenPaymentUtility { WHERE DeveloperName = :developerName ]; if (adyenAdapters.isEmpty()) { - throw new AdyenAsyncAdapter.GatewayException(NO_ADYEN_ADAPTER_BY_NAME + developerName); + throw new AdyenGatewayAdapter.GatewayException(NO_ADYEN_ADAPTER_BY_NAME + developerName); } return adyenAdapters[0]; } @@ -63,7 +63,7 @@ public with sharing class AdyenPaymentUtility { WHERE Merchant_Account__c = :merchantAccountName ]; if (adyenAdapters.isEmpty()) { - throw new AdyenAsyncAdapter.GatewayException(NO_ADYEN_ADAPTER_BY_MERCHANT + merchantAccountName); + throw new AdyenGatewayAdapter.GatewayException(NO_ADYEN_ADAPTER_BY_MERCHANT + merchantAccountName); } return adyenAdapters[0]; } @@ -109,7 +109,7 @@ public with sharing class AdyenPaymentUtility { Id = :paymentAuthId ]; if (paymentAuthorizations.isEmpty()) { - throw new AdyenAsyncAdapter.GatewayException(NO_PAYMENT_AUTH_FOUND_BY_ID + paymentAuthId); + throw new AdyenGatewayAdapter.GatewayException(NO_PAYMENT_AUTH_FOUND_BY_ID + paymentAuthId); } return paymentAuthorizations[0]; } @@ -383,7 +383,7 @@ public with sharing class AdyenPaymentUtility { CommercePayments.PaymentsHttp paymentsHttp = new CommercePayments.PaymentsHttp(); HttpResponse response = paymentsHttp.send(request); if (response.getStatusCode() != 200 && response.getStatusCode() != 201) { - throw new AdyenAsyncAdapter.GatewayException('Adyen Checkout API returned: ' + response.getStatusCode() + ', body: ' + response.getBody()); + throw new AdyenGatewayAdapter.GatewayException('Adyen Checkout API returned: ' + response.getStatusCode() + ', body: ' + response.getBody()); } else { return response; } diff --git a/force-app/main/default/classes/AdyenRefundHelper.cls b/force-app/main/default/classes/AdyenRefundHelper.cls index 8419653..6deccd5 100644 --- a/force-app/main/default/classes/AdyenRefundHelper.cls +++ b/force-app/main/default/classes/AdyenRefundHelper.cls @@ -8,7 +8,6 @@ public with sharing class AdyenRefundHelper { * @param refundRequest The CommercePayments.ReferencedRefundRequest Object. * @return refundResponse The CommercePayments.ReferencedRefundResponse Object. * - * @see AdyenClient */ public static CommercePayments.GatewayResponse refund(CommercePayments.ReferencedRefundRequest refundRequest) { Payment payment = AdyenPaymentUtility.retrievePayment(refundRequest.paymentId); @@ -22,7 +21,7 @@ public with sharing class AdyenRefundHelper { errorMessage = 'Payment Amount Missing'; } if (errorMessage != null) { - throw new AdyenAsyncAdapter.GatewayException(errorMessage); + throw new AdyenGatewayAdapter.GatewayException(errorMessage); } String pspReference = payment.PaymentAuthorization?.GatewayRefNumber != null ? payment.PaymentAuthorization.GatewayRefNumber : payment.GatewayRefNumber; diff --git a/force-app/main/default/classes/TestDataFactory.cls b/force-app/main/default/classes/TestDataFactory.cls index cda35b3..bf773ee 100644 --- a/force-app/main/default/classes/TestDataFactory.cls +++ b/force-app/main/default/classes/TestDataFactory.cls @@ -11,7 +11,7 @@ public class TestDataFactory { public static final String ACTIVE_CURRENCY = [SELECT IsoCode FROM CurrencyType WHERE IsActive = TRUE LIMIT 1].IsoCode; public static final String ASSERT_PRICE_MESSAGE = 'For input price of '; - public static AdyenAsyncAdapter adyenAdapter = new AdyenAsyncAdapter(); + public static AdyenGatewayAdapter adyenAdapter = new AdyenGatewayAdapter(); public static Account createAccount() { return new Account(Name = 'Test Account'); diff --git a/force-app/main/default/gatewayProviderPaymentMethodTypes/AlternativePaymentMethod.gatewayProviderPaymentMethodType-meta.xml b/force-app/main/default/gatewayProviderPaymentMethodTypes/AlternativePaymentMethod.gatewayProviderPaymentMethodType-meta.xml index b6cd26e..ac31588 100644 --- a/force-app/main/default/gatewayProviderPaymentMethodTypes/AlternativePaymentMethod.gatewayProviderPaymentMethodType-meta.xml +++ b/force-app/main/default/gatewayProviderPaymentMethodTypes/AlternativePaymentMethod.gatewayProviderPaymentMethodType-meta.xml @@ -2,7 +2,7 @@ AdyenComponent Alternative Payment Method - Adyen + Adyen_OMS_Provider AlternativePaymentMethod AlternativePaymentMethod.Alternative_Payment_Method diff --git a/force-app/main/default/paymentGatewayProviders/Adyen.paymentGatewayProvider-meta.xml b/force-app/main/default/paymentGatewayProviders/Adyen_OMS_Provider.paymentGatewayProvider-meta.xml similarity index 63% rename from force-app/main/default/paymentGatewayProviders/Adyen.paymentGatewayProvider-meta.xml rename to force-app/main/default/paymentGatewayProviders/Adyen_OMS_Provider.paymentGatewayProvider-meta.xml index fa2a9ad..a1a215b 100644 --- a/force-app/main/default/paymentGatewayProviders/Adyen.paymentGatewayProvider-meta.xml +++ b/force-app/main/default/paymentGatewayProviders/Adyen_OMS_Provider.paymentGatewayProvider-meta.xml @@ -1,6 +1,6 @@ - AdyenAsyncAdapter + AdyenGatewayAdapter Yes - SalesforceOrderManagement-Adyen + Adyen OMS Provider diff --git a/manifest/package.xml b/manifest/package.xml index 5550f09..cf8a41f 100644 --- a/manifest/package.xml +++ b/manifest/package.xml @@ -13,6 +13,8 @@ AdyenPaymentUtilityTest AdyenRefundHelper TestDataFactory + AdyenGatewayAdapter + AdyenGatewayAdapterTest ApexClass From bcf4f6368681f08c89adeed8148d64174d7b81ba Mon Sep 17 00:00:00 2001 From: daniloc Date: Thu, 25 Jul 2024 10:29:48 +0200 Subject: [PATCH 15/21] feat: release 3.0.0 --- force-app/main/default/classes/AdyenOMSConstants.cls | 4 ++-- .../Adyen_Adapter.AdyenDefault.md-meta.xml | 2 +- sfdx-project.json | 11 ++++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/force-app/main/default/classes/AdyenOMSConstants.cls b/force-app/main/default/classes/AdyenOMSConstants.cls index c1f0351..0a5928f 100644 --- a/force-app/main/default/classes/AdyenOMSConstants.cls +++ b/force-app/main/default/classes/AdyenOMSConstants.cls @@ -8,9 +8,9 @@ public with sharing class AdyenOMSConstants { public static final String EXTERNAL_PLATFORM_NAME_FOR_APP_INFO = 'Adyen Salesforce OMS'; public static final String ADYEN_LIBRARY_NAME_FOR_APP_INFO = 'adyen-apex-api-library'; - public static final String ADYEN_LIBRARY_VERSION_FOR_APP_INFO = '3.0.1'; + public static final String ADYEN_LIBRARY_VERSION_FOR_APP_INFO = '3.2.0'; public static final String MERCHANT_APP_NAME_FOR_APP_INFO = 'adyen-salesforce-oms'; - public static final String MERCHANT_APP_VERSION_FOR_APP_INFO = '2.1.2'; + public static final String MERCHANT_APP_VERSION_FOR_APP_INFO = '3.0.0'; public static final String CARD_PAYMENT_METHOD_OBJECT = 'CardPaymentMethod'; public static final String ALTERNATIVE_PAYMENT_METHOD_OBJECT = 'AlternativePaymentMethod'; diff --git a/force-app/main/default/customMetadata/Adyen_Adapter.AdyenDefault.md-meta.xml b/force-app/main/default/customMetadata/Adyen_Adapter.AdyenDefault.md-meta.xml index dd0b968..4c2deef 100644 --- a/force-app/main/default/customMetadata/Adyen_Adapter.AdyenDefault.md-meta.xml +++ b/force-app/main/default/customMetadata/Adyen_Adapter.AdyenDefault.md-meta.xml @@ -12,7 +12,7 @@ Endpoint_Api_Version__c - /v70 + /v71 Endpoint_Path__c diff --git a/sfdx-project.json b/sfdx-project.json index 4a0a866..49027da 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -5,12 +5,12 @@ "default": true, "package": "Adyen Salesforce Order Management", "definitionFile": "config/project-scratch-def.json", - "versionName": "version 2.1", - "versionNumber": "2.1.2.NEXT", + "versionName": "version 3.0", + "versionNumber": "3.0.0.NEXT", "ancestorVersion": "HIGHEST", "dependencies": [ { - "package": "API Library Apex Adyen@3.0.1-1" + "package": "API Library Apex Adyen@3.2.0-2" } ] } @@ -20,10 +20,11 @@ "sourceApiVersion": "60.0", "packageAliases": { "Adyen Salesforce Order Management": "0Ho4T000000blPMSAY", - "API Library Apex Adyen@3.0.1-1": "04tRP0000000A5pYAE", + "API Library Apex Adyen@3.2.0-2": "04tRP0000000j5BYAQ", "Adyen Salesforce Order Management@2.0.0": "04t4T000001y7C0QAI", "Adyen Salesforce Order Management@2.1.0": "04tRP00000002BVYAY", "Adyen Salesforce Order Management@2.1.1-1": "04tRP0000000A7RYAU", - "Adyen Salesforce Order Management@2.1.2-1": "04tRP0000000AFVYA2" + "Adyen Salesforce Order Management@2.1.2-1": "04tRP0000000AFVYA2", + "Adyen Salesforce Order Management@3.0.0-1": "04tRP0000000lF3YAI" } } \ No newline at end of file From 70187063381a39ed8583c68f5e74c126fcbd487e Mon Sep 17 00:00:00 2001 From: daniloc Date: Tue, 30 Jul 2024 16:35:01 +0200 Subject: [PATCH 16/21] task: new build with new gateway provider name and gateway class --- sfdx-project.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sfdx-project.json b/sfdx-project.json index 49027da..d613f67 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -10,7 +10,7 @@ "ancestorVersion": "HIGHEST", "dependencies": [ { - "package": "API Library Apex Adyen@3.2.0-2" + "package": "API Library Apex Adyen@3.2.0-5" } ] } @@ -20,11 +20,11 @@ "sourceApiVersion": "60.0", "packageAliases": { "Adyen Salesforce Order Management": "0Ho4T000000blPMSAY", - "API Library Apex Adyen@3.2.0-2": "04tRP0000000j5BYAQ", + "API Library Apex Adyen@3.2.0-5": "04tRP0000000lwbYAA", "Adyen Salesforce Order Management@2.0.0": "04t4T000001y7C0QAI", "Adyen Salesforce Order Management@2.1.0": "04tRP00000002BVYAY", "Adyen Salesforce Order Management@2.1.1-1": "04tRP0000000A7RYAU", "Adyen Salesforce Order Management@2.1.2-1": "04tRP0000000AFVYA2", - "Adyen Salesforce Order Management@3.0.0-1": "04tRP0000000lF3YAI" + "Adyen Salesforce Order Management@3.0.0-5": "04tRP0000000m1RYAQ" } } \ No newline at end of file From cfc7ec5c3eeefa70a3dff5307a7e9787de8026f1 Mon Sep 17 00:00:00 2001 From: Danilo Cardoso Date: Wed, 31 Jul 2024 15:18:41 +0200 Subject: [PATCH 17/21] fix: package incompatibility with new order save behavior (#45) Co-authored-by: daniloc --- config/project-scratch-def.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/project-scratch-def.json b/config/project-scratch-def.json index 65b4977..4e42f5d 100644 --- a/config/project-scratch-def.json +++ b/config/project-scratch-def.json @@ -1,7 +1,7 @@ { "orgName": "Adyen_OMS", "edition": "Developer", - "features": ["EnableSetPasswordInApi", "OrderManagement", "MultiCurrency"], + "features": ["EnableSetPasswordInApi", "OrderManagement", "MultiCurrency", "OrderSaveBehaviorBoth"], "settings": { "lightningExperienceSettings": { "enableS1DesktopEnabled": true From fbe0f6484970c63056d863aa2e563296f1b2992f Mon Sep 17 00:00:00 2001 From: Danilo Cardoso Date: Wed, 31 Jul 2024 15:34:37 +0200 Subject: [PATCH 18/21] Removing duplicated code (#44) * task: changing names to avoid conflict * fix: code duplication * fix: code duplication and comments * fix: admin profile permission classes name --------- Co-authored-by: daniloc --- .../default/classes/AdyenAsyncAdapter.cls | 33 ++++- .../default/classes/AdyenAsyncAdapterTest.cls | 131 ------------------ .../AdyenAsyncAdapterTest.cls-meta.xml | 5 - .../default/profiles/Admin.profile-meta.xml | 6 +- manifest/package.xml | 1 - 5 files changed, 31 insertions(+), 145 deletions(-) delete mode 100644 force-app/main/default/classes/AdyenAsyncAdapterTest.cls delete mode 100644 force-app/main/default/classes/AdyenAsyncAdapterTest.cls-meta.xml diff --git a/force-app/main/default/classes/AdyenAsyncAdapter.cls b/force-app/main/default/classes/AdyenAsyncAdapter.cls index 3f94429..c2c72bd 100644 --- a/force-app/main/default/classes/AdyenAsyncAdapter.cls +++ b/force-app/main/default/classes/AdyenAsyncAdapter.cls @@ -3,21 +3,40 @@ * This way when upgrading the package conflicts will be avoided. * The new one should be AdyenGatewayAdapter. */ -global with sharing class AdyenAsyncAdapter implements CommercePayments.PaymentGatewayAdapter, CommercePayments.PaymentGatewayAsyncAdapter { +global with sharing class AdyenAsyncAdapter implements CommercePayments.PaymentGatewayAdapter, + CommercePayments.PaymentGatewayAsyncAdapter { + global AdyenAsyncAdapter() {} + /** + * The entry point for processing payment requests. Returns the response from the payment gateway. + * Accepts the gateway context request and handover the operation to AdyenPaymentHelper to call the appropriate capture or refund operation. + * + * @param paymentGatewayContext from SF + * @return CommercePayments.GatewayResponse + * + * @implNotes + * [CAPTURE] is called after setting Fulfillment.Status to 'Fulfilled' which in turns fires processes + * and flows to create invoices which ultimately fires this. + * + * [REFUND] is called by using the action on the order summary page (left hand side). + * + */ global CommercePayments.GatewayResponse processRequest(CommercePayments.PaymentGatewayContext paymentGatewayContext) { - try { - return AdyenPaymentHelper.handleFulfillmentOrderStatusChange(paymentGatewayContext); - } catch (Exception ex) { - return new CommercePayments.GatewayErrorResponse('500', ex.getMessage()); - } + return AdyenPaymentHelper.handleFulfillmentOrderStatusChange(paymentGatewayContext); } - global CommercePayments.GatewayNotificationResponse processNotification(CommercePayments.PaymentGatewayNotificationContext gatewayNotificationContext) { + /** + * Listens to the incoming async notification callback from Adyen and handover to AdyenPaymentHelper for processing + * + * @param gatewayNotificationContext from SF + * @return CommercePayments.GatewayNotificationResponse + */ + public CommercePayments.GatewayNotificationResponse processNotification(CommercePayments.PaymentGatewayNotificationContext gatewayNotificationContext) { return AdyenPaymentHelper.handleAsyncNotificationCallback(gatewayNotificationContext); } public class GatewayException extends Exception {} + } \ No newline at end of file diff --git a/force-app/main/default/classes/AdyenAsyncAdapterTest.cls b/force-app/main/default/classes/AdyenAsyncAdapterTest.cls deleted file mode 100644 index daf0cef..0000000 --- a/force-app/main/default/classes/AdyenAsyncAdapterTest.cls +++ /dev/null @@ -1,131 +0,0 @@ -@IsTest -private class AdyenAsyncAdapterTest { - @TestSetup - static void makeData() { - Account acct = TestDataFactory.createAccount(); - insert acct; - TestDataFactory.insertBasicPaymentRecords(acct.Id, null); - } - - @IsTest - static void testCaptureOutboundSuccess() { - Test.setMock(HttpCalloutMock.class, new TestDataFactory.EchoHttpMock()); - - Test.startTest(); - Id authId = [SELECT Id FROM PaymentAuthorization ORDER BY CreatedDate DESC LIMIT 1].Id; - CommercePayments.CaptureRequest captureRequest = new CommercePayments.CaptureRequest(TestDataFactory.TEST_AMOUNT, authId); - CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(captureRequest, CommercePayments.RequestType.Capture); - CommercePayments.GatewayResponse captureResponse = TestDataFactory.adyenAdapter.processRequest(context); - Test.stopTest(); - - Assert.isTrue(captureResponse.toString().contains('[capture-received]')); - Assert.isTrue(captureResponse.toString().contains(TestDataFactory.TEST_PSP_REFERENCE)); - } - - @IsTest - static void testCaptureOutboundFailure() { - Test.setMock(HttpCalloutMock.class, new TestDataFactory.FailureResponse()); - - Test.startTest(); - Id authId = [SELECT Id FROM PaymentAuthorization ORDER BY CreatedDate DESC LIMIT 1].Id; - CommercePayments.CaptureRequest captureRequest = new CommercePayments.CaptureRequest(TestDataFactory.TEST_AMOUNT, authId); - CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(captureRequest, CommercePayments.RequestType.Capture); - CommercePayments.GatewayResponse gatewayResponse = TestDataFactory.adyenAdapter.processRequest(context); - Test.stopTest(); - - Assert.isInstanceOfType(gatewayResponse, CommercePayments.GatewayErrorResponse.class); - Assert.isTrue(gatewayResponse.toString().containsIgnoreCase('400')); - } - - @IsTest - static void testCaptureOutboundMissingPaymentAuthorization() { - Test.setMock(HttpCalloutMock.class, new TestDataFactory.EchoHttpMock()); - - Test.startTest(); - CommercePayments.CaptureRequest captureRequest = new CommercePayments.CaptureRequest(TestDataFactory.TEST_AMOUNT, null); - CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(captureRequest, CommercePayments.RequestType.Capture); - CommercePayments.GatewayResponse gatewayResponse = TestDataFactory.adyenAdapter.processRequest(context); - Test.stopTest(); - - Assert.isInstanceOfType(gatewayResponse, CommercePayments.GatewayErrorResponse.class); - Assert.isTrue(gatewayResponse.toString().containsIgnoreCase(AdyenPaymentUtility.NO_PAYMENT_AUTH_FOUND_BY_ID)); - } - - @IsTest - static void testCaptureOutboundMissingAmount() { - Test.setMock(HttpCalloutMock.class, new TestDataFactory.EchoHttpMock()); - Id authId = [SELECT Id FROM PaymentAuthorization ORDER BY CreatedDate DESC LIMIT 1].Id; - Test.startTest(); - CommercePayments.CaptureRequest captureRequest = new CommercePayments.CaptureRequest(null, authId); - CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(captureRequest, CommercePayments.RequestType.Capture); - CommercePayments.GatewayResponse gatewayResponse = TestDataFactory.adyenAdapter.processRequest(context); - Test.stopTest(); - Assert.isInstanceOfType(gatewayResponse, CommercePayments.GatewayErrorResponse.class); - Assert.isTrue(gatewayResponse.toString().containsIgnoreCase('Payment Amount Missing')); - } - - @IsTest - static void testCaptureInboundSuccess() { - AdyenPaymentHelper.TEST_NOTIFICATION_REQUEST_BODY = TestDataFactory.createNotificationRequestBody('CAPTURE', TestDataFactory.TEST_PSP_REFERENCE); - - Test.startTest(); - CommercePayments.GatewayNotificationResponse captureResponse = TestDataFactory.adyenAdapter.processNotification(null); - Test.stopTest(); - - Assert.isFalse(captureResponse.toString().containsIgnoreCase('error')); - } - - @IsTest - static void testRefundOutboundSuccess() { - Test.setMock(HttpCalloutMock.class, new TestDataFactory.EchoHttpMock()); - - Test.startTest(); - Id paymentId = [SELECT Id FROM Payment ORDER BY CreatedDate DESC LIMIT 1].Id; - CommercePayments.ReferencedRefundRequest refundRequest = new CommercePayments.ReferencedRefundRequest(TestDataFactory.TEST_AMOUNT, paymentId); - CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(refundRequest, CommercePayments.RequestType.ReferencedRefund); - CommercePayments.GatewayResponse refundResponse = TestDataFactory.adyenAdapter.processRequest(context); - Test.stopTest(); - - Assert.isTrue(refundResponse.toString().contains('received')); - } - - @IsTest - static void testRefundOutboundFailure() { - Test.setMock(HttpCalloutMock.class, new TestDataFactory.FailureResponse()); - - Test.startTest(); - Id paymentId = [SELECT Id FROM Payment ORDER BY CreatedDate DESC LIMIT 1].Id; - CommercePayments.ReferencedRefundRequest refundRequest = new CommercePayments.ReferencedRefundRequest(TestDataFactory.TEST_AMOUNT, paymentId); - CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(refundRequest, CommercePayments.RequestType.ReferencedRefund); - CommercePayments.GatewayResponse refundResponse = TestDataFactory.adyenAdapter.processRequest(context); - Test.stopTest(); - - Assert.isInstanceOfType(refundResponse, CommercePayments.GatewayErrorResponse.class); - Assert.isTrue(refundResponse.toString().containsIgnoreCase('400')); - } - - @IsTest - static void testRefundOutboundMissingPayment() { - Test.setMock(HttpCalloutMock.class, new TestDataFactory.EchoHttpMock()); - Test.startTest(); - CommercePayments.ReferencedRefundRequest refundRequest = new CommercePayments.ReferencedRefundRequest(TestDataFactory.TEST_AMOUNT, null); - CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(refundRequest, CommercePayments.RequestType.ReferencedRefund); - CommercePayments.GatewayResponse gatewayResponse = TestDataFactory.adyenAdapter.processRequest(context); - Test.stopTest(); - Assert.isInstanceOfType(gatewayResponse, CommercePayments.GatewayErrorResponse.class); - Assert.isTrue(gatewayResponse.toString().containsIgnoreCase(AdyenPaymentUtility.NO_PAYMENT_FOUND_BY_ID)); - } - - @IsTest - static void testRefundOutboundMissingAmount() { - Test.setMock(HttpCalloutMock.class, new TestDataFactory.EchoHttpMock()); - Id paymentId = [SELECT Id FROM Payment ORDER BY CreatedDate DESC LIMIT 1].Id; - Test.startTest(); - CommercePayments.ReferencedRefundRequest refundRequest = new CommercePayments.ReferencedRefundRequest(null, paymentId); - CommercePayments.PaymentGatewayContext context = new CommercePayments.PaymentGatewayContext(refundRequest, CommercePayments.RequestType.ReferencedRefund); - CommercePayments.GatewayResponse gatewayResponse = TestDataFactory.adyenAdapter.processRequest(context); - Test.stopTest(); - Assert.isInstanceOfType(gatewayResponse, CommercePayments.GatewayErrorResponse.class); - Assert.isTrue(gatewayResponse.toString().containsIgnoreCase('Payment Amount Missing')); - } -} \ No newline at end of file diff --git a/force-app/main/default/classes/AdyenAsyncAdapterTest.cls-meta.xml b/force-app/main/default/classes/AdyenAsyncAdapterTest.cls-meta.xml deleted file mode 100644 index f5e18fd..0000000 --- a/force-app/main/default/classes/AdyenAsyncAdapterTest.cls-meta.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - 60.0 - Active - diff --git a/force-app/main/default/profiles/Admin.profile-meta.xml b/force-app/main/default/profiles/Admin.profile-meta.xml index 7ce5dbf..254386f 100644 --- a/force-app/main/default/profiles/Admin.profile-meta.xml +++ b/force-app/main/default/profiles/Admin.profile-meta.xml @@ -5,7 +5,11 @@ true - AdyenAsyncAdapterTest + AdyenGatewayAdapter + true + + + AdyenGatewayAdapterTest true diff --git a/manifest/package.xml b/manifest/package.xml index cf8a41f..c48061a 100644 --- a/manifest/package.xml +++ b/manifest/package.xml @@ -2,7 +2,6 @@ AdyenAsyncAdapter - AdyenAsyncAdapterTest AdyenAuthorisationHelper AdyenAuthorisationHelperTest AdyenCaptureHelper From 390d2b8fec5a5b15691585c3f321e0ffba8aa7e1 Mon Sep 17 00:00:00 2001 From: daniloc Date: Thu, 25 Jul 2024 10:29:48 +0200 Subject: [PATCH 19/21] feat: release 3.0.0 --- force-app/main/default/classes/AdyenOMSConstants.cls | 4 ++-- .../Adyen_Adapter.AdyenDefault.md-meta.xml | 2 +- sfdx-project.json | 11 ++++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/force-app/main/default/classes/AdyenOMSConstants.cls b/force-app/main/default/classes/AdyenOMSConstants.cls index c1f0351..0a5928f 100644 --- a/force-app/main/default/classes/AdyenOMSConstants.cls +++ b/force-app/main/default/classes/AdyenOMSConstants.cls @@ -8,9 +8,9 @@ public with sharing class AdyenOMSConstants { public static final String EXTERNAL_PLATFORM_NAME_FOR_APP_INFO = 'Adyen Salesforce OMS'; public static final String ADYEN_LIBRARY_NAME_FOR_APP_INFO = 'adyen-apex-api-library'; - public static final String ADYEN_LIBRARY_VERSION_FOR_APP_INFO = '3.0.1'; + public static final String ADYEN_LIBRARY_VERSION_FOR_APP_INFO = '3.2.0'; public static final String MERCHANT_APP_NAME_FOR_APP_INFO = 'adyen-salesforce-oms'; - public static final String MERCHANT_APP_VERSION_FOR_APP_INFO = '2.1.2'; + public static final String MERCHANT_APP_VERSION_FOR_APP_INFO = '3.0.0'; public static final String CARD_PAYMENT_METHOD_OBJECT = 'CardPaymentMethod'; public static final String ALTERNATIVE_PAYMENT_METHOD_OBJECT = 'AlternativePaymentMethod'; diff --git a/force-app/main/default/customMetadata/Adyen_Adapter.AdyenDefault.md-meta.xml b/force-app/main/default/customMetadata/Adyen_Adapter.AdyenDefault.md-meta.xml index dd0b968..4c2deef 100644 --- a/force-app/main/default/customMetadata/Adyen_Adapter.AdyenDefault.md-meta.xml +++ b/force-app/main/default/customMetadata/Adyen_Adapter.AdyenDefault.md-meta.xml @@ -12,7 +12,7 @@ Endpoint_Api_Version__c - /v70 + /v71 Endpoint_Path__c diff --git a/sfdx-project.json b/sfdx-project.json index 4a0a866..49027da 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -5,12 +5,12 @@ "default": true, "package": "Adyen Salesforce Order Management", "definitionFile": "config/project-scratch-def.json", - "versionName": "version 2.1", - "versionNumber": "2.1.2.NEXT", + "versionName": "version 3.0", + "versionNumber": "3.0.0.NEXT", "ancestorVersion": "HIGHEST", "dependencies": [ { - "package": "API Library Apex Adyen@3.0.1-1" + "package": "API Library Apex Adyen@3.2.0-2" } ] } @@ -20,10 +20,11 @@ "sourceApiVersion": "60.0", "packageAliases": { "Adyen Salesforce Order Management": "0Ho4T000000blPMSAY", - "API Library Apex Adyen@3.0.1-1": "04tRP0000000A5pYAE", + "API Library Apex Adyen@3.2.0-2": "04tRP0000000j5BYAQ", "Adyen Salesforce Order Management@2.0.0": "04t4T000001y7C0QAI", "Adyen Salesforce Order Management@2.1.0": "04tRP00000002BVYAY", "Adyen Salesforce Order Management@2.1.1-1": "04tRP0000000A7RYAU", - "Adyen Salesforce Order Management@2.1.2-1": "04tRP0000000AFVYA2" + "Adyen Salesforce Order Management@2.1.2-1": "04tRP0000000AFVYA2", + "Adyen Salesforce Order Management@3.0.0-1": "04tRP0000000lF3YAI" } } \ No newline at end of file From b10307cecd2efc6190edec2ff8b2bc2c22a138e0 Mon Sep 17 00:00:00 2001 From: daniloc Date: Tue, 30 Jul 2024 16:35:01 +0200 Subject: [PATCH 20/21] task: new build with new gateway provider name and gateway class --- sfdx-project.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sfdx-project.json b/sfdx-project.json index 49027da..d613f67 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -10,7 +10,7 @@ "ancestorVersion": "HIGHEST", "dependencies": [ { - "package": "API Library Apex Adyen@3.2.0-2" + "package": "API Library Apex Adyen@3.2.0-5" } ] } @@ -20,11 +20,11 @@ "sourceApiVersion": "60.0", "packageAliases": { "Adyen Salesforce Order Management": "0Ho4T000000blPMSAY", - "API Library Apex Adyen@3.2.0-2": "04tRP0000000j5BYAQ", + "API Library Apex Adyen@3.2.0-5": "04tRP0000000lwbYAA", "Adyen Salesforce Order Management@2.0.0": "04t4T000001y7C0QAI", "Adyen Salesforce Order Management@2.1.0": "04tRP00000002BVYAY", "Adyen Salesforce Order Management@2.1.1-1": "04tRP0000000A7RYAU", "Adyen Salesforce Order Management@2.1.2-1": "04tRP0000000AFVYA2", - "Adyen Salesforce Order Management@3.0.0-1": "04tRP0000000lF3YAI" + "Adyen Salesforce Order Management@3.0.0-5": "04tRP0000000m1RYAQ" } } \ No newline at end of file From 3beebf02df6f0642d343e01af8468f65ea158275 Mon Sep 17 00:00:00 2001 From: daniloc Date: Thu, 1 Aug 2024 10:13:32 +0200 Subject: [PATCH 21/21] feat: final build release --- sfdx-project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfdx-project.json b/sfdx-project.json index d613f67..43fdc80 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -25,6 +25,6 @@ "Adyen Salesforce Order Management@2.1.0": "04tRP00000002BVYAY", "Adyen Salesforce Order Management@2.1.1-1": "04tRP0000000A7RYAU", "Adyen Salesforce Order Management@2.1.2-1": "04tRP0000000AFVYA2", - "Adyen Salesforce Order Management@3.0.0-5": "04tRP0000000m1RYAQ" + "Adyen Salesforce Order Management@3.0.0-6": "04tRP0000000mW5YAI" } } \ No newline at end of file