Skip to content

Commit

Permalink
Merge pull request #45 from Adyen/release/3.1.0
Browse files Browse the repository at this point in the history
Release/3.1.0.1
  • Loading branch information
dcardos authored May 8, 2024
2 parents 2bcb353 + 6fa729e commit c7f9546
Show file tree
Hide file tree
Showing 28 changed files with 480 additions and 136 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @maassenbas @shubhamvijaivargiya @dcardos
* @amihajlovski @dcardos @shanikantsingh @shubhamvijaivargiya @zenit2001
12 changes: 12 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!-- 🎉 Thank you for submitting a pull request! 🎉 -->

## Summary
Describe the changes proposed in this pull request:
- What is the motivation for this change?
- What existing problem does this pull request solve?


## Tested scenarios
Description of tested scenarios:

**Fixed issue**: <!-- #-prefixed issue number -->
23 changes: 23 additions & 0 deletions .github/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
changelog:
exclude:
labels:
- ignore-for-release
- excluded
categories:
- title: Breaking Changes 🛠
labels:
- breaking-change
- title: New Features 🎉
labels:
- new
- enhancement
- title: Fixes ❤️‍🩹
labels:
- bug
- fix
- title: Refactored 🌪
labels:
- refactor
- title: Other Changes
labels:
- "*"
24 changes: 11 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
# Adyen Apex API Library

> **This is a Beta release**
This repository contains the client and the models that are used for the requests/responses for Adyen's Checkout API.
This library will be used in combination with other Salesforce managed packages from Adyen (e.g. Salesforce B2B Commerce).
This repository contains the client and the models that are used for the requests/responses for Adyen's Checkout API.
This library will be used in combination with other Salesforce managed packages from Adyen (e.g. Salesforce B2B Commerce).

The library supports the following services:
* [Checkout API](https://docs.adyen.com/api-explorer/#/CheckoutService/v64/overview) - Payments: Our latest integration for accepting online payments. Current supported version: **v64**

* [Checkout API](https://docs.adyen.com/api-explorer/#/CheckoutService/v64/overview) - Payments: Our latest integration for accepting online payments. Current supported version: **v71**

## Prerequisites
- [Adyen test account](https://docs.adyen.com/get-started-with-adyen)
- Access to your [Salesforce org](https://login.salesforce.com/).

## Installation
The package needs to be installed on your Salesforce org as explained on their [docs](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_dev2gp_install_pkg.htm)
The package needs to be installed on your Salesforce org as explained on their [docs](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_dev2gp_install_pkg.htm).
Link for the package installation: [Apex Library in AppExchange](https://appexchange.salesforce.com/appxListingDetail?listingId=a0N3u00000PraxuEAB)

## Using the library
After installing the package on your Salesforce org, other managed packages from Adyen will use this library as reference model.
The requests/responses can be (de)serialized into the models and send to Adyen's API endpoints through the client.
After installing the package on your Salesforce org, other managed packages from Adyen will use this library as reference model.
The requests/responses can be (de)serialized into the models and send to Adyen's API endpoints through the client.

## Contributing
We encourage you to contribute to this repository, so everyone can benefit from new features, bug fixes, and any other improvements.
We encourage you to contribute to this repository, so everyone can benefit from new features, bug fixes, and any other improvements.
Have a look at our [contributing guidelines](https://github.com/Adyen/.github/blob/master/CONTRIBUTING.md) to find out how to raise a pull request.

## Support
If you have a feature request, or spotted a bug or a technical problem, create a GitHub issue. For other questions, contact our [support team](https://support.adyen.com/hc/en-us/requests/new?ticket_form_id=360000705420).
If you have a feature request, or spotted a bug or a technical problem, create a GitHub issue. For other questions, contact our [support team](https://support.adyen.com/hc/en-us/requests/new?ticket_form_id=360000705420).

## License
## License
MIT license. For more information, see the LICENSE file.

## See also
* [Adyen docs](https://docs.adyen.com/)
* [API Explorer](https://docs.adyen.com/api-explorer/)

2 changes: 1 addition & 1 deletion force-app/main/default/classes/AdyenConstants.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<apiVersion>59.0</apiVersion>
<status>Active</status>
</ApexClass>
36 changes: 36 additions & 0 deletions force-app/main/default/classes/AuthenticationData.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
@NamespaceAccessible
public without sharing class AuthenticationData {
/**
* Indicates when 3D Secure authentication should be attempted. This overrides all other rules, including Dynamic 3D Secure settings.
* Possible values:
* always: Perform 3D Secure authentication.
* never: Don't perform 3D Secure authentication. If PSD2 SCA or other national regulations require authentication, the transaction gets declined.
*/
@NamespaceAccessible
public String attemptAuthentication { get; set; }

/**
* If set to true, you will only perform the 3D Secure 2 authentication, and not the payment authorisation.
* Default: false.
*/
@NamespaceAccessible
public Boolean authenticationOnly { get; set; }

/**
* Object with additional parameters for the 3D Secure authentication flow.
*/
@NamespaceAccessible
public ThreeDSRequestData threeDSRequestData { get; set; }

@NamespaceAccessible
public AuthenticationData() {}

@NamespaceAccessible
public static AuthenticationData getExample() {
AuthenticationData authenticationData = new AuthenticationData();
authenticationData.attemptAuthentication = 'always';
authenticationData.authenticationOnly = false;
authenticationData.threeDSRequestData = ThreeDSRequestData.getExample();
return authenticationData;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<status>Active</status>
</ApexClass>
15 changes: 15 additions & 0 deletions force-app/main/default/classes/AuthenticationDataTest.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Created by daniloc on 11/01/2024.
*/

@IsTest
private class AuthenticationDataTest {
@IsTest
static void getExampleTest() {
AuthenticationData authenticationData = AuthenticationData.getExample();
Assert.isNotNull(authenticationData);
Assert.isNotNull(authenticationData.attemptAuthentication);
Assert.isNotNull(authenticationData.authenticationOnly);
Assert.isNotNull(authenticationData.threeDSRequestData);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<status>Active</status>
</ApexClass>
54 changes: 34 additions & 20 deletions force-app/main/default/classes/CheckoutPaymentsAction.cls
Original file line number Diff line number Diff line change
Expand Up @@ -9,63 +9,68 @@
* Do not edit the class manually.
*/

@namespaceAccessible
@NamespaceAccessible
public with sharing class CheckoutPaymentsAction {
/**
* When the redirect URL must be accessed via POST, use this data to post to the redirect URL.
* @return data
*/
@namespaceAccessible
@NamespaceAccessible
public Map<String, String> data { get; set; }

/**
* Specifies the HTTP method, for example GET or POST.
* @return method
*/
@namespaceAccessible
@NamespaceAccessible
public String method { get; set; }

/**
* When non-empty, contains a value that you must submit to the `/payments/details` endpoint. In some cases, required for polling.
* @return paymentData
*/
@namespaceAccessible
@NamespaceAccessible
public String paymentData { get; set; }

/**
* Specifies the payment method.
* @return paymentMethodType
*/
@namespaceAccessible
@NamespaceAccessible
public String paymentMethodType { get; set; }

/**
* Specifies the token of the action.
* @return token
*/
@namespaceAccessible
@NamespaceAccessible
public String token { get; set; }

/**
* Specifies the type of the action.
* @return type
*/
@namespaceAccessible
@NamespaceAccessible
public String type { get; set; }

/**
* Specifies the URL to redirect to.
* @return url
*/
@namespaceAccessible
@NamespaceAccessible
public String url { get; set; }

@namespaceAccessible
/**
* A token needed to authorise a payment.
*/
@NamespaceAccessible
public String authorisationToken { get; set; }

/**
* A subtype of the token.
*/
@NamespaceAccessible
public String subtype { get; set; }

@NamespaceAccessible
public CheckoutPaymentsAction() {
data = new Map<String, String>();
}

@namespaceAccessible
@NamespaceAccessible
public static CheckoutPaymentsAction getExample() {
CheckoutPaymentsAction checkoutPaymentsAction = new CheckoutPaymentsAction();
checkoutPaymentsAction.data = new Map<String, String>{'key'=>''};
Expand All @@ -74,10 +79,13 @@ public with sharing class CheckoutPaymentsAction {
checkoutPaymentsAction.paymentMethodType = '';
checkoutPaymentsAction.type = '';
checkoutPaymentsAction.url = '';
checkoutPaymentsAction.authorisationToken = '';
checkoutPaymentsAction.token = '';
checkoutPaymentsAction.subtype = '';
return checkoutPaymentsAction;
}

@namespaceAccessible
@NamespaceAccessible
public Boolean equals(Object obj) {
if (obj instanceof CheckoutPaymentsAction) {
CheckoutPaymentsAction checkoutRedirectAction = (CheckoutPaymentsAction) obj;
Expand All @@ -86,12 +94,15 @@ public with sharing class CheckoutPaymentsAction {
&& this.paymentData == checkoutRedirectAction.paymentData
&& this.paymentMethodType == checkoutRedirectAction.paymentMethodType
&& this.type == checkoutRedirectAction.type
&& this.url == checkoutRedirectAction.url;
&& this.url == checkoutRedirectAction.url
&& this.token == checkoutRedirectAction.token
&& this.authorisationToken == checkoutRedirectAction.authorisationToken
&& this.subtype == checkoutRedirectAction.subtype;
}
return false;
}

@namespaceAccessible
@NamespaceAccessible
public Integer hashCode() {
Integer hashCode = 43;
hashCode = (17 * hashCode) + (data == null ? 0 : System.hashCode(data));
Expand All @@ -100,6 +111,9 @@ public with sharing class CheckoutPaymentsAction {
hashCode = (17 * hashCode) + (paymentMethodType == null ? 0 : System.hashCode(paymentMethodType));
hashCode = (17 * hashCode) + (type == null ? 0 : System.hashCode(type));
hashCode = (17 * hashCode) + (url == null ? 0 : System.hashCode(url));
hashCode = (17 * hashCode) + (subtype == null ? 0 : System.hashCode(subtype));
hashCode = (17 * hashCode) + (authorisationToken == null ? 0 : System.hashCode(authorisationToken));
hashCode = (17 * hashCode) + (token == null ? 0 : System.hashCode(token));
return hashCode;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<apiVersion>59.0</apiVersion>
<status>Active</status>
</ApexClass>
15 changes: 15 additions & 0 deletions force-app/main/default/classes/CheckoutPaymentsActionTest.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@IsTest
private class CheckoutPaymentsActionTest {
@IsTest
static void equalsTest() {
// given
CheckoutPaymentsAction action1 = CheckoutPaymentsAction.getExample();
CheckoutPaymentsAction action2 = CheckoutPaymentsAction.getExample();
action2.type = 'threeDS2';
// then
Assert.areEqual(action1, action1);
Assert.areNotEqual(action2, action1);
Assert.areEqual(action1.hashCode(), action1.hashCode());
Assert.areNotEqual(action2.hashCode(), action1.hashCode());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>59.0</apiVersion>
<status>Active</status>
</ApexClass>
57 changes: 57 additions & 0 deletions force-app/main/default/classes/HMACValidator.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
@namespaceAccessible
public with sharing class HMACValidator {
private static final String HMAC_SHA256_ALGORITHM = 'HmacSHA256';
private static final String DATA_SEPARATOR = ':';

@namespaceAccessible
public class HmacValidationException extends Exception {}

@TestVisible
private String calculateHMAC(NotificationRequestItem notificationRequestItem, String key){
List<Object> payloadParts = new List<Object>{
notificationRequestItem.pspReference,
notificationRequestItem.originalReference,
notificationRequestItem.merchantAccountCode,
notificationRequestItem.merchantReference,
notificationRequestItem.amount.value,
notificationRequestItem.amount.currency_x,
notificationRequestItem.eventCode,
notificationRequestItem.success
};

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

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

Blob hmac = Crypto.generateMac(HMAC_SHA256_ALGORITHM, binaryPayload, binaryKey);
String hmacBase64 = EncodingUtil.base64Encode(hmac);
return hmacBase64;
}

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

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

return bitwiseComparison == 0;
}

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

return compareHMAC(calculatedSignature, hmacSignature);
}
}
5 changes: 5 additions & 0 deletions force-app/main/default/classes/HMACValidator.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>59.0</apiVersion>
<status>Active</status>
</ApexClass>
Loading

0 comments on commit c7f9546

Please sign in to comment.