content: package com.adyen.checkout.api;
  
  import java.io.IOException;
  import java.util.Arrays;
  import java.util.UUID;
  
  import com.adyen.checkout.ApplicationProperty;
  import com.adyen.service.checkout.PaymentsApi;
  import jakarta.servlet.http.HttpServletRequest;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.http.ResponseEntity;
  import org.springframework.web.bind.annotation.*;
  import org.springframework.web.servlet.view.RedirectView;
  import com.adyen.Client;
  import com.adyen.enums.Environment;
  import com.adyen.model.checkout.*;
  import com.adyen.service.exception.ApiException;
  
  /**
   * REST controller for using Adyen checkout API
   */
  @RestController
  @RequestMapping("/api")
  public class CheckoutResource {
      private final Logger log = LoggerFactory.getLogger(CheckoutResource.class);
  
      private final ApplicationProperty applicationProperty;
  
      private final PaymentsApi paymentsApi;
  
      public CheckoutResource(ApplicationProperty applicationProperty) {
  
          this.applicationProperty = applicationProperty;
  
          if(applicationProperty.getApiKey() == null) {
              log.warn("ADYEN_KEY is UNDEFINED");
              throw new RuntimeException("ADYEN_KEY is UNDEFINED");
          }
  
          var client = new Client(applicationProperty.getApiKey(), Environment.TEST);
          this.paymentsApi = new PaymentsApi(client);
      }
  
      /**
       * {@code POST  /getPaymentMethods} : Get valid payment methods.
       *
       * @return the {@link ResponseEntity} with status {@code 200 (Ok)} and with body the paymentMethods response.
       * @throws IOException  from Adyen API.
       * @throws ApiException from Adyen API.
       */
      @PostMapping("/getPaymentMethods")
      public ResponseEntity<PaymentMethodsResponse> paymentMethods() throws IOException, ApiException {
          var paymentMethodsRequest = new PaymentMethodsRequest();
          paymentMethodsRequest.setMerchantAccount(this.applicationProperty.getMerchantAccount());
          paymentMethodsRequest.setChannel(PaymentMethodsRequest.ChannelEnum.WEB);
  
          log.info("REST request to get Adyen payment methods {}", paymentMethodsRequest);
          var response = paymentsApi.paymentMethods(paymentMethodsRequest);
          return ResponseEntity.ok()
              .body(response);
      }
  
      /**
       * {@code POST  /initiatePayment} : Make a payment.
       *
       * @return the {@link ResponseEntity} with status {@code 200 (Ok)} and with body the paymentMethods response.
       * @throws IOException  from Adyen API.
       * @throws ApiException from Adyen API.
       */
      @PostMapping("/initiatePayment")
      public ResponseEntity<PaymentResponse> payments(@RequestHeader String host, @RequestBody PaymentRequest body, HttpServletRequest request) throws IOException, ApiException {
          var paymentRequest = new PaymentRequest();
  
          var orderRef = UUID.randomUUID().toString();
          var amount = new Amount()
              .currency("EUR")
              .value(10000L); // value is 100€ in minor units
  
          paymentRequest.setMerchantAccount(this.applicationProperty.getMerchantAccount()); // required
          paymentRequest.setChannel(PaymentRequest.ChannelEnum.WEB);
          paymentRequest.setReference(orderRef); // required
          paymentRequest.setReturnUrl(request.getScheme() + "://" + host + "/api/handleShopperRedirect?orderRef=" + orderRef);
  
          paymentRequest.setAmount(amount);
          // set countryCode and lineItems required for some payment methods (ie Klarna)
          paymentRequest.setCountryCode("NL");
          paymentRequest.setLineItems(Arrays.asList(
              new LineItem().quantity(1L).amountIncludingTax(5000L).description("Sunglasses"),
              new LineItem().quantity(1L).amountIncludingTax(5000L).description("Headphones"))
          );
  
          var authenticationData = new AuthenticationData();
          authenticationData.setAttemptAuthentication(AuthenticationData.AttemptAuthenticationEnum.ALWAYS);
          // add the following lines for Native 3DS2:
          //var threeDSRequestData = new ThreeDSRequestData();
          //threeDSRequestData.setNativeThreeDS(ThreeDSRequestData.NativeThreeDSEnum.PREFERRED);
          //authenticationData.setThreeDSRequestData(threeDSRequestData);
  
          paymentRequest.setAuthenticationData(authenticationData);
  
          // required for 3ds2 redirect flow
          paymentRequest.setOrigin(request.getScheme() + "://" + host);
          // required for 3ds2
          paymentRequest.setBrowserInfo(body.getBrowserInfo());
          // required by some issuers for 3ds2
          paymentRequest.setShopperIP(request.getRemoteAddr());
          paymentRequest.setPaymentMethod(body.getPaymentMethod());
  
          // we strongly recommend that you the billingAddress in your request
          // card schemes require this for channel web, iOS, and Android implementations
  //        paymentRequest.setBillingAddress(new BillingAddress()
  //            .city("Amsterdam")
  //            .country("NL")
  //            .postalCode("1234AA")
  //            .street("Street 1")
  //            .houseNumberOrName("1"));
  
          // emailShopper recommended for fraud checks
  //         paymentRequest.setShopperEmail("youremail@email.com");
  
          log.info("REST request to make Adyen payment {}", paymentRequest);
          var response = paymentsApi.payments(paymentRequest);
          return ResponseEntity.ok()
              .body(response);
      }
  
      /**
       * {@code POST  /submitAdditionalDetails} : Make a payment.
       *
       * @return the {@link ResponseEntity} with status {@code 200 (Ok)} and with body the paymentMethods response.
       * @throws IOException  from Adyen API.
       * @throws ApiException from Adyen API.
       */
      @PostMapping("/submitAdditionalDetails")
      public ResponseEntity<PaymentDetailsResponse> payments(@RequestBody PaymentDetailsRequest detailsRequest) throws IOException, ApiException {
          log.info("REST request to make Adyen payment details {}", detailsRequest);
          var response = paymentsApi.paymentsDetails(detailsRequest);
          return ResponseEntity.ok()
              .body(response);
      }
  
      /**
       * {@code GET  /handleShopperRedirect} : Handle redirect during payment.
       *
       * @return the {@link RedirectView} with status {@code 302}
       * @throws IOException  from Adyen API.
       * @throws ApiException from Adyen API.
       */
      @GetMapping("/handleShopperRedirect")
      public RedirectView redirect(@RequestParam(required = false) String payload, @RequestParam(required = false) String redirectResult, @RequestParam String orderRef) throws IOException, ApiException {
          var detailsRequest = new PaymentDetailsRequest();
  
          PaymentCompletionDetails details = new PaymentCompletionDetails();
          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
              details.redirectResult(redirectResult);
          } else if (payload != null && !payload.isEmpty()) {
              details.payload(payload);
          }
  
          detailsRequest.setDetails(details);
          return getRedirectView(detailsRequest);
      }
  
      private RedirectView getRedirectView(final PaymentDetailsRequest detailsRequest) throws ApiException, IOException {
          log.info("REST request to handle payment redirect {}", detailsRequest);
          var response = paymentsApi.paymentsDetails(detailsRequest);
          var redirectURL = "/result/";
          switch (response.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=" + response.getResultCode());
      }
  }
  