Skip to content

Commit

Permalink
Merge pull request #46 from Adyen/release/2.2.0
Browse files Browse the repository at this point in the history
Release/2.2.0
  • Loading branch information
dcardos authored Jun 10, 2024
2 parents 553ead5 + 2ae5c33 commit fef118c
Show file tree
Hide file tree
Showing 25 changed files with 646 additions and 187 deletions.
60 changes: 60 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions force-app/main/default/classes/AdyenAsyncAdapterTest.cls
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ global class AdyenAsyncAdapterTest {
Assert.isTrue(postAuthResponse.toString().contains('success'));
}

@IsTest
static void processNotificationTest() {
// given
AdyenAsyncAdapter adapter = new AdyenAsyncAdapter();
// when
CommercePayments.GatewayNotificationResponse postAuthResponse = (CommercePayments.GatewayNotificationResponse)adapter.processNotification(null);
// then
Assert.isNull(postAuthResponse);
}

// legacy global class that cannot be removed
global class EchoHttpMock implements HttpCalloutMock {
global HttpResponse respond(HttpRequest req) {
Expand Down
8 changes: 6 additions & 2 deletions force-app/main/default/classes/AdyenB2BConstants.cls
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
public with sharing class AdyenB2BConstants {
public static final String EXTERNAL_PLATFORM_NAME_FOR_APP_INFO = 'Adyen Salesforce B2B LWR';
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.1.0';
public static final String ADYEN_LIBRARY_VERSION_FOR_APP_INFO = '3.1.1';
public static final String MERCHANT_APP_NAME_FOR_APP_INFO = 'adyen-salesforce-b2b-lwr';
public static final String MERCHANT_APP_VERSION_FOR_APP_INFO = '2.1.0';
public static final String MERCHANT_APP_VERSION_FOR_APP_INFO = '2.2.0';

public static final String PSP_REFERENCE_KEY = 'pspReference';

public static final String DEFAULT_PAYMENT_GATEWAY_NAME = 'Adyen';
public static final String CARD_PAYMENT_METHOD_TYPE = 'scheme';
public static final String SEPA_DIRECT_DEBIT = 'sepadirectdebit';
public static final String ACH_DIRECT_DEBIT = 'ach';
public static final String BANCONTACT_CARD_PAYMENT_METHOD_TYPE = 'bcmc';
public static final String BANCONTACT_MOBILE_PAYMENT_METHOD_TYPE = 'bcmc_mobile';
public static final String NOTIFICATION_REQUEST_TYPE_AUTHORISE = 'authorisation';
Expand Down
2 changes: 1 addition & 1 deletion force-app/main/default/classes/AdyenB2BUtils.cls
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public with sharing class AdyenB2BUtils {
req.setBody(compatibleBody);
HttpResponse response = new Http().send(req);
if (response.getStatusCode() != 200) {
throw new AdyenCustomException('Adyen API returned: ' + response.getStatusCode());
throw new AdyenCustomException('Adyen API returned: ' + response.getStatusCode() + ', body: ' + response.getBody());
} else {
return response;
}
Expand Down
114 changes: 48 additions & 66 deletions force-app/main/default/classes/AdyenDropInController.cls
Original file line number Diff line number Diff line change
@@ -1,30 +1,20 @@
public with sharing class AdyenDropInController {
public class CriticalException extends Exception {}

@AuraEnabled(Cacheable=true)
public static String getMetadataClientKey(String adyenAdapterName) {
return AdyenB2BUtils.retrieveAdyenAdapter(adyenAdapterName).Client_Key__c;
}

@AuraEnabled
public static PaymentMethodsAndClientKey fetchPaymentMethods(String adyenAdapterName) {
public static String fetchPaymentMethods(String adyenAdapterName) {
WebCart cart;
try {
Adyen_Adapter__mdt adyenAdapter = AdyenB2BUtils.retrieveAdyenAdapter(adyenAdapterName);
cart = AdyenB2BUtils.fetchCartDetails();
Amount requestAmount = new Amount();
requestAmount.value = (cart.GrandTotalAmount * AdyenB2BUtils.getAmountMultiplier(cart.CurrencyIsoCode)).round(System.RoundingMode.HALF_UP);
requestAmount.currency_x = cart.CurrencyIsoCode;
String shopperLocale = UserInfo.getLocale();
String[] localeParts = shopperLocale.split('_');
String countryCode = localeParts.size() > 1 ? localeParts[1] : '';

PaymentMethodsRequest paymentMethodsRequest = new PaymentMethodsRequest();
paymentMethodsRequest.merchantAccount = adyenAdapter.Merchant_Account__c;
paymentMethodsRequest.amount = requestAmount;
paymentMethodsRequest.allowedPaymentMethods = new List<String>{AdyenB2BConstants.CARD_PAYMENT_METHOD_TYPE};
paymentMethodsRequest.blockedPaymentMethods = new List<String>{AdyenB2BConstants.BANCONTACT_CARD_PAYMENT_METHOD_TYPE, AdyenB2BConstants.BANCONTACT_MOBILE_PAYMENT_METHOD_TYPE};
paymentMethodsRequest.shopperLocale = shopperLocale;
paymentMethodsRequest.countryCode = countryCode;

PaymentMethodsRequest paymentMethodsRequest = createPaymentMethodsRequest(cart, adyenAdapter.Merchant_Account__c);
HttpResponse result = AdyenB2BUtils.makePostRequest(adyenAdapter, 'Payment_Methods_Endpoint__c', JSON.serialize(paymentMethodsRequest));
PaymentMethodsAndClientKey paymentMethodsAndClientKey = new PaymentMethodsAndClientKey(result.getBody(), adyenAdapter.Client_Key__c);
return paymentMethodsAndClientKey;
return result.getBody();
} catch (Exception ex) {
System.debug(LoggingLevel.ERROR, ex.getMessage());
LogEntry.insertLogException(ex, 'Fetch Payment Method Error', AdyenDropInController.class.getName(), 'fetchPaymentMethods');
Expand All @@ -38,9 +28,9 @@ public with sharing class AdyenDropInController {
try {
cart = AdyenB2BUtils.fetchCartDetails();
Adyen_Adapter__mdt adyenAdapter = AdyenB2BUtils.retrieveAdyenAdapter(clientDetails.adyenAdapterName);
PaymentsRequest paymentsRequest = buildPaymentsRequest(clientDetails, cart, adyenAdapter);
PaymentsRequest paymentsRequest = AdyenPaymentRequestBuilder.create(clientDetails, cart, adyenAdapter);

HttpResponse result = AdyenB2BUtils.makePostRequest(adyenAdapter, 'Payments_Endpoint__c', JSON.serialize(paymentsRequest));
HttpResponse result = AdyenB2BUtils.makePostRequest(adyenAdapter, 'Payments_Endpoint__c', JSON.serialize(paymentsRequest, true));
PaymentsResponse paymentsResp = (PaymentsResponse)JSON.deserialize(result.getBody(), PaymentsResponse.class);
persistPaymentDetails(cart, paymentsResp, clientDetails);

Expand Down Expand Up @@ -72,9 +62,29 @@ public with sharing class AdyenDropInController {
}
}

private static PaymentMethodsRequest createPaymentMethodsRequest(WebCart cart, String merchantAccount) {
Amount requestAmount = new Amount();
requestAmount.value = (cart.GrandTotalAmount * AdyenB2BUtils.getAmountMultiplier(cart.CurrencyIsoCode)).round(System.RoundingMode.HALF_UP);
requestAmount.currency_x = cart.CurrencyIsoCode;
String shopperLocale = UserInfo.getLocale();
String[] localeParts = shopperLocale.split('_');
String countryCode = localeParts.size() > 1 ? localeParts[1] : '';

PaymentMethodsRequest paymentMethodsRequest = new PaymentMethodsRequest();
paymentMethodsRequest.merchantAccount = merchantAccount;
paymentMethodsRequest.amount = requestAmount;
paymentMethodsRequest.allowedPaymentMethods = new List<String>{AdyenB2BConstants.CARD_PAYMENT_METHOD_TYPE, AdyenB2BConstants.SEPA_DIRECT_DEBIT, AdyenB2BConstants.ACH_DIRECT_DEBIT};
paymentMethodsRequest.blockedPaymentMethods = new List<String>{AdyenB2BConstants.BANCONTACT_CARD_PAYMENT_METHOD_TYPE, AdyenB2BConstants.BANCONTACT_MOBILE_PAYMENT_METHOD_TYPE};
paymentMethodsRequest.shopperLocale = shopperLocale;
paymentMethodsRequest.countryCode = countryCode;
paymentMethodsRequest.shopperReference = cart.OwnerId;

return paymentMethodsRequest;
}

private static void persistPaymentDetails(WebCart cart, PaymentsResponse paymentsResp, ClientDetails clientDetails) {
if (AdyenB2BUtils.isPaymentAccepted(paymentsResp.resultCode) || paymentsResp.action != null) {
ConnectApi.PostAuthorizationResponse postAuthorizationResponse = callPostAuthorize(clientDetails.cardData, cart, paymentsResp);
ConnectApi.PostAuthorizationResponse postAuthorizationResponse = callPostAuthorize(clientDetails, cart, paymentsResp);
if (postAuthorizationResponse.gatewayResponse.gatewayResultCode.equalsIgnoreCase('success')) {
updateCartDetails(postAuthorizationResponse, cart, clientDetails.getCompatibleBillingAddress());
} else {
Expand All @@ -85,51 +95,28 @@ public with sharing class AdyenDropInController {
}
}



@TestVisible
private static PaymentsRequest buildPaymentsRequest(ClientDetails clientDetails, WebCart cart, Adyen_Adapter__mdt adyenAdapter) {
Amount requestAmount = new Amount();
requestAmount.value = (cart.GrandTotalAmount * AdyenB2BUtils.getAmountMultiplier(cart.CurrencyIsoCode)).round(System.RoundingMode.HALF_UP);
requestAmount.currency_x = cart.CurrencyIsoCode;

PaymentsRequest paymentsRequest = new PaymentsRequest();
paymentsRequest.paymentMethod = clientDetails.getPaymentMethodDetails();
paymentsRequest.merchantAccount = adyenAdapter.Merchant_Account__c;
paymentsRequest.amount = requestAmount;
paymentsRequest.reference = cart.Id;
paymentsRequest.shopperReference = cart.OwnerId;
paymentsRequest.shopperEmail = UserInfo.getUserEmail();
paymentsRequest.returnUrl = AdyenB2BUtils.getSiteUrl() + '/payment-processing';
paymentsRequest.billingAddress = clientDetails.getCompatibleBillingAddress();
paymentsRequest.browserInfo = clientDetails.getBrowserInfo();
paymentsRequest.applicationInfo = AdyenB2BUtils.getApplicationInfo(adyenAdapter.System_Integrator_Name__c);
paymentsRequest.channel = 'Web';
paymentsRequest.origin = AdyenB2BUtils.getSiteUrl();

paymentsRequest.authenticationData = new AuthenticationData();
paymentsRequest.authenticationData.threeDSRequestData = new ThreeDSRequestData();
paymentsRequest.authenticationData.threeDSRequestData.nativeThreeDS = 'preferred';

return paymentsRequest;
}

private static ConnectApi.PostAuthorizationResponse callPostAuthorize(ClientDetails.CardData cardData, WebCart webCart, PaymentsResponse paymentsResp) {
private static ConnectApi.PostAuthorizationResponse callPostAuthorize(ClientDetails clientDetails, WebCart webCart, PaymentsResponse paymentsResp) {
ConnectApi.PostAuthRequest postAuthRequest = new ConnectApi.PostAuthRequest();
postAuthRequest.accountId = webCart.AccountId;
postAuthRequest.amount = webCart.GrandTotalAmount;
postAuthRequest.currencyIsoCode = webCart.CurrencyIsoCode;
postAuthRequest.effectiveDate = System.now();
postAuthRequest.paymentGatewayId = AdyenB2BUtils.fetchAdyenGatewayId();
postAuthRequest.paymentMethod = new ConnectApi.PostAuthApiPaymentMethodRequest();
postAuthRequest.paymentMethod.cardPaymentMethod = createCardPayMethodRequest(cardData);
postAuthRequest.paymentMethod.cardPaymentMethod.accountId = webCart.AccountId;
if (clientDetails.getPaymentMethodDetails() instanceof CardDetails){
postAuthRequest.paymentMethod.cardPaymentMethod = createCardPayMethodRequest(clientDetails.cardData);
postAuthRequest.paymentMethod.cardPaymentMethod.accountId = webCart.AccountId;
} else {
postAuthRequest.paymentMethod.alternativePaymentMethod = createAltPayMethodRequest(paymentsResp.pspReference);
postAuthRequest.paymentMethod.alternativePaymentMethod.accountId = webCart.AccountId;
postAuthRequest.paymentMethod.alternativePaymentMethod.name = clientDetails.paymentMethodType;
}
postAuthRequest.paymentGroup = new ConnectApi.PaymentGroupRequest();
postAuthRequest.paymentGroup.createPaymentGroup = true;
postAuthRequest.paymentGroup.currencyIsoCode = webCart.CurrencyIsoCode;

Map<String,String> additionalData = new Map<String,String>();
additionalData.put('pspReference', paymentsResp.pspReference);
additionalData.put(AdyenB2BConstants.PSP_REFERENCE_KEY, paymentsResp.pspReference);
postAuthRequest.additionalData = additionalData;

ConnectApi.PostAuthorizationResponse postAuthorizationResponse = Test.isRunningTest() ? mockPostAuthResponse() : ConnectApi.Payments.postAuth(postAuthRequest);
Expand All @@ -154,6 +141,13 @@ public with sharing class AdyenDropInController {
return cardPaymentMethodRequest;
}

private static ConnectApi.AlternativePaymentMethod createAltPayMethodRequest(String pspReference) {
ConnectApi.AlternativePaymentMethod apmRequest = new ConnectApi.AlternativePaymentMethod();
apmRequest.gatewayToken = pspReference;
apmRequest.gatewayTokenDetails = 'PSP reference';
return apmRequest;
}

private static void updateCartDetails(ConnectApi.PostAuthorizationResponse postAuthorizationResponse, WebCart webCart, Address billingAddress) {
try {
webCart.PaymentGroupId = postAuthorizationResponse.paymentGroup.id;
Expand Down Expand Up @@ -184,18 +178,6 @@ public with sharing class AdyenDropInController {
LogEntry.insertLogInformation('Failed Payment Attempt', cartId, 'The payment failed with code: ' + paymentsResp.resultCode.name(), details);
}

public class PaymentMethodsAndClientKey {
@AuraEnabled
public String paymentMethodsResponse;
@AuraEnabled
public String clientKey;

PaymentMethodsAndClientKey(String paymentMethodsResponse, String clientKey) {
this.paymentMethodsResponse = paymentMethodsResponse;
this.clientKey = clientKey;
}
}

public class MinimalPaymentResponse {
@AuraEnabled
public Boolean paymentSuccessful;
Expand Down
Loading

0 comments on commit fef118c

Please sign in to comment.