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