Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Workshop/solution-v6 using Adyen.Web Dropin v6.6.0 & Java Library v.31.3.0 #24

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
313 changes: 173 additions & 140 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ repositories {
}

dependencies {
// [Step 5] Add the Adyen Java library here
// [Step 1] Add the Adyen Java library here - We've already included this for you in the line below:
implementation 'com.adyen:adyen-java-api-library:31.3.0'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/com/adyen/workshop/MainApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ public static void main(String[] args) {
public void init() {
log.info("\n----------------------------------------------------------\n\t" +
"Application is running on http://localhost:" + applicationConfiguration.getServerPort() +
"\nAPI KEY:" + (applicationConfiguration.getAdyenApiKey() != null) +
"\nMerchant Account:" + (applicationConfiguration.getAdyenMerchantAccount() != null) +
"\nClient Key:" + (applicationConfiguration.getAdyenClientKey() != null) +
"\nAdyen API Key: " + (applicationConfiguration.getAdyenApiKey() != null) +
"\nAdyen Merchant Account: " + applicationConfiguration.getAdyenMerchantAccount() +
"\nAdyen Client Key:" + applicationConfiguration.getAdyenClientKey() +
"\n----------------------------------------------------------");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public DependencyInjectionConfiguration(ApplicationConfiguration applicationConf
Client client() {
// Step 4
var config = new Config();
config.setApiKey(applicationConfiguration.getAdyenApiKey()); // We now use the Adyen API Key
config.setEnvironment(Environment.TEST); // Sets the environment to TEST
return new Client(config);
}

Expand Down
147 changes: 133 additions & 14 deletions src/main/java/com/adyen/workshop/controllers/ApiController.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import com.adyen.service.checkout.PaymentsApi;
import com.adyen.service.exception.ApiException;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.coyote.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
Expand All @@ -15,6 +14,9 @@

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
Expand All @@ -32,34 +34,151 @@ public ApiController(ApplicationConfiguration applicationConfiguration, Payments
this.paymentsApi = paymentsApi;
}

// Step 0
@GetMapping("/hello-world")
public ResponseEntity<String> helloWorld() throws Exception {
// Step 0
return ResponseEntity.ok()
.body("This is the 'Hello World' from the workshop - You've successfully finished step 0!");
return ResponseEntity.ok().body("This is the 'Hello World' from the workshop - You've successfully finished step 0!");
}

// Step 7
@PostMapping("/api/paymentMethods")
public ResponseEntity<PaymentMethodsResponse> paymentMethods() throws IOException, ApiException {
// Step 7
return null;
var paymentMethodsRequest = new PaymentMethodsRequest();
paymentMethodsRequest.setMerchantAccount(applicationConfiguration.getAdyenMerchantAccount());

log.info("Retrieving available Payment Methods from Adyen {}", paymentMethodsRequest);
var response = paymentsApi.paymentMethods(paymentMethodsRequest);
log.info("Payment Methods response from Adyen {}", response);
return ResponseEntity.ok().body(response);
}

// Step 9 - Implement the /payments call to Adyen.
@PostMapping("/api/payments")
public ResponseEntity<PaymentResponse> payments(@RequestHeader String host, @RequestBody PaymentRequest body, HttpServletRequest request) throws IOException, ApiException {
// Step 9
return null;
var paymentRequest = new PaymentRequest();

var amount = new Amount()
.currency("EUR")
.value(9998L);
paymentRequest.setAmount(amount);
paymentRequest.setMerchantAccount(applicationConfiguration.getAdyenMerchantAccount());
paymentRequest.setChannel(PaymentRequest.ChannelEnum.WEB);
paymentRequest.setPaymentMethod(body.getPaymentMethod());

// Step 12 3DS2 Redirect - Add the following additional parameters to your existing payment request for 3DS2 Redirect:
// Note: Visa requires additional properties to be sent in the request, see documentation for Redirect 3DS2: https://docs.adyen.com/online-payments/3d-secure/redirect-3ds2/web-drop-in/#make-a-payment
var authenticationData = new AuthenticationData();
authenticationData.setAttemptAuthentication(AuthenticationData.AttemptAuthenticationEnum.ALWAYS);
paymentRequest.setAuthenticationData(authenticationData);

// Add the following lines, if you want to enable the Native 3DS2 flow:
// Note: Visa requires additional properties to be sent in the request, see documentation for Native 3DS2: https://docs.adyen.com/online-payments/3d-secure/native-3ds2/web-drop-in/#make-a-payment
//authenticationData.setThreeDSRequestData(new ThreeDSRequestData().nativeThreeDS(ThreeDSRequestData.NativeThreeDSEnum.PREFERRED));
//paymentRequest.setAuthenticationData(authenticationData);

paymentRequest.setOrigin(request.getScheme() + "://" + host);
paymentRequest.setBrowserInfo(body.getBrowserInfo());
paymentRequest.setShopperIP(request.getRemoteAddr());
paymentRequest.setShopperInteraction(PaymentRequest.ShopperInteractionEnum.ECOMMERCE);

var billingAddress = new BillingAddress();
billingAddress.setCity("Amsterdam");
billingAddress.setCountry("NL");
billingAddress.setPostalCode("1012KK");
billingAddress.setStreet("Rokin");
billingAddress.setHouseNumberOrName("49");
paymentRequest.setBillingAddress(billingAddress);

var orderRef = UUID.randomUUID().toString();
paymentRequest.setReference(orderRef);
// The returnUrl field basically means: Once done with the payment, where should the application redirect you?
paymentRequest.setReturnUrl(request.getScheme() + "://" + host + "/handleShopperRedirect?orderRef=" + orderRef); // Example: Turns into http://localhost:8080/handleShopperRedirect?orderRef=354fa90e-0858-4d2f-92b9-717cb8e18173

// Step 19 (optional) - shopperEmail, shopperReference, lineItems is required for klarna
LineItem lineItem1 = new LineItem()
.quantity(1L)
.taxPercentage(2100L)
.amountIncludingTax(4999L)
.imageUrl("https://adyen.com")
.description("The best sunglasses")
.id("uniqueId-1")
.productUrl("https://adyen.com");

LineItem lineItem2 = new LineItem()
.quantity(1L)
.taxPercentage(2100L)
.amountIncludingTax(4999L)
.imageUrl("https://adyen.com")
.description("The best headphones")
.id("uniqueId-2")
.productUrl("https://adyen.com");

paymentRequest.setCountryCode("NL");
paymentRequest.setShopperReference("shopperReference");
paymentRequest.setShopperEmail("[email protected]");
paymentRequest.setLineItems(Arrays.asList(lineItem1, lineItem2));



// Step 11 - Add the idempotency key
var requestOptions = new RequestOptions();
requestOptions.setIdempotencyKey(UUID.randomUUID().toString());

log.info("PaymentsRequest {}", paymentRequest);
var response = paymentsApi.payments(paymentRequest, requestOptions); // Notice how we're adding this property to our existing code*
log.info("PaymentsResponse {}", response);
return ResponseEntity.ok().body(response);
}

// Step 13 - Handle details call (triggered after Native 3DS2 flow)
@PostMapping("/api/payments/details")
public ResponseEntity<PaymentDetailsResponse> paymentsDetails(@RequestBody PaymentDetailsRequest detailsRequest) throws IOException, ApiException {
// Step 13
return null;
public ResponseEntity<PaymentDetailsResponse> paymentsDetails(@RequestBody PaymentDetailsRequest detailsRequest) throws IOException, ApiException
{
log.info("PaymentDetailsRequest {}", detailsRequest);
var response = paymentsApi.paymentsDetails(detailsRequest);
log.info("PaymentDetailsResponse {}", response);
return ResponseEntity.ok().body(response);
}

@GetMapping("/api/handleShopperRedirect")
// Step 14 - Handle Redirect 3DS2 during payment.
@GetMapping("/handleShopperRedirect")
public RedirectView redirect(@RequestParam(required = false) String payload, @RequestParam(required = false) String redirectResult) throws IOException, ApiException {
// Step 14
return null;
var paymentDetailsRequest = new PaymentDetailsRequest();

PaymentCompletionDetails paymentCompletionDetails = new PaymentCompletionDetails();

// Handle redirect result or payload
if (redirectResult != null && !redirectResult.isEmpty()) {
// For redirect, you are redirected to an Adyen domain to complete the 3DS2 challenge
// After completing the 3DS2 challenge, you get the redirect result from Adyen in the returnUrl
// We then pass on the redirectResult
paymentCompletionDetails.redirectResult(redirectResult);
} else if (payload != null && !payload.isEmpty()) {
paymentCompletionDetails.payload(payload);
}

paymentDetailsRequest.setDetails(paymentCompletionDetails);

var paymentsDetailsResponse = paymentsApi.paymentsDetails(paymentDetailsRequest);
log.info("PaymentsDetailsResponse {}", paymentsDetailsResponse);

// Handle response and redirect user accordingly
var redirectURL = "/result/";
switch (paymentsDetailsResponse.getResultCode()) {
case AUTHORISED:
redirectURL += "success";
break;
case PENDING:
case RECEIVED:
redirectURL += "pending";
break;
case REFUSED:
redirectURL += "failed";
break;
default:
redirectURL += "error";
break;
}
return new RedirectView(redirectURL + "?reason=" + paymentsDetailsResponse.getResultCode());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,29 @@ public WebhookController(ApplicationConfiguration applicationConfiguration, HMAC

@PostMapping("/webhooks")
public ResponseEntity<String> webhooks(@RequestBody String json) throws Exception {
// Step 16
return null;
log.info("Received: {}", json);
var notificationRequest = NotificationRequest.fromJson(json);
var notificationRequestItem = notificationRequest.getNotificationItems().stream().findFirst();

try {
NotificationRequestItem item = notificationRequestItem.get();

// Step 16 - Validate the HMAC signature using the ADYEN_HMAC_KEY
if (!hmacValidator.validateHMAC(item, this.applicationConfiguration.getAdyenHmacKey())) {
log.warn("Could not validate HMAC signature for incoming webhook message: {}", item);
return ResponseEntity.unprocessableEntity().build();
}

// Success, log it for now
log.info("Received webhook with event {}", item.toString());

return ResponseEntity.accepted().build();
} catch (SignatureException e) {
// Handle invalid signature
return ResponseEntity.unprocessableEntity().build();
} catch (Exception e) {
// Handle all other errors
return ResponseEntity.status(500).build();
}
}
}
Loading
Loading