Skip to content

Commit

Permalink
Update README and functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Kwok-he-Chu committed Sep 24, 2024
1 parent 8588a23 commit 20e8f04
Show file tree
Hide file tree
Showing 8 changed files with 46 additions and 33 deletions.
5 changes: 3 additions & 2 deletions .gitpod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ tasks:
./gradlew bootJar
command: |
# Start application on port 8080
echo "Starting application..."
./gradlew bootRun
#echo "Starting application..."
#./gradlew bootRun
echo "Start the application by running `./gradlew bootRun` > You can use [control + C] to stop running your application."
ports:
- port: 8080
Expand Down
51 changes: 31 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ In this workshop, you'll learn how to:

### Start - Step-by-Step Guide:

**Step 0.** Build the project using `./gradlew bootRun` and see if it works. If you can visit `http://localhost:8080/hello-world`, `https://8080-adyenexampl-adyenstepby-xxxxxx21.ws-eu114.gitpod.io/hello-world` (Gitpod) or `https://xxxx.github.dev/hello-world` (codespaces), this means it works!

**Step 0.** Build the project using `./gradlew bootRun` and see if it works.

If you see the following message in your console logs, it means that you've successfully ran the application.
A browser should open with the following screen: **"Workshop: Build Your Own Adyen Payment Integration"**

```
----------------------------------------------------------
Expand All @@ -124,7 +124,7 @@ If you see the following message in your console logs, it means that you've succ


**Step 4.** Add the following values from step 1-3 to `ApplicationConfiguration.java` in `/main/java/com/adyen/workshop/configurations`:
- Best practice: export the vars as follows so that the Spring Boot framework can automatically inject your variables on startup.
- Best practice: export the variables as follows so that the Spring Boot framework can automatically inject your variables on startup.
- If you're using gitpod/codespaces, you can export your variables as follows in your terminal:
- If you've used gitpod before, the program will inject previously used environment variables as configured in [https://gitpod.io/variables](https://gitpod.io/variables).
```
Expand Down Expand Up @@ -201,7 +201,7 @@ We're now set up to do the `/paymentMethods`, `/payments` and `/payments/details
![Adyen.Web Drop-in Advanced Flow](./docs/images/drop-in-flow.jpg)


**Step 7.** Let's prepare our backend (`com/adyen/workshop/controllers`) to [retrieve a list of available payment methods](https://docs.adyen.com/online-payments/build-your-integration/advanced-flow/?platform=Web&integration=Drop-in&version=5.63.0&programming_language=java#web-advanced-flow-post-payment-methods-request). Go to `ApiController.java` and use the `paymentsApi` to make `/paymentMethods`-request to Adyen.
**Step 7.** Let's prepare our backend (`/controllers/ApiController.java`) to [retrieve a list of available payment methods](https://docs.adyen.com/online-payments/build-your-integration/advanced-flow/?platform=Web&integration=Drop-in&version=5.63.0&programming_language=java#web-advanced-flow-post-payment-methods-request). Go to `ApiController.java` and use the `paymentsApi` to make `/paymentMethods`-request to Adyen.


<details>
Expand All @@ -228,7 +228,11 @@ We're now set up to do the `/paymentMethods`, `/payments` and `/payments/details

**Step 8.** In the frontend (`adyenWebImplementation.js`), let's make a request to this `/api/paymentMethods` endpoint and display the payment methods to the shopper.

We automatically pass on your public `ADYEN_CLIENT_KEY` to your frontend, you can access this variable using `clientKey`.
We automatically pass on your public `ADYEN_CLIENT_KEY` to your frontend (see `checkout.html`), you can access this variable using `clientKey`. If you want to find it, here it is:
```
<div id="clientKey" class="hidden" th:text="${clientKey}"></div>
```



Create the configuration for the `AdyenCheckout`-instance, call the `/api/paymentMethods/`-endpoint, create the `AdyenCheckOut()`-instance, and mount it to `"payment"-div` container (see `/resources/templates/checkout.html`).
Expand Down Expand Up @@ -305,7 +309,7 @@ Here are some helpful notes if you do not see any payment methods show up on you
* **Invalid origin:** Have you added the correct origin URLs that allow your `Adyen Drop-in` to be loaded by the page?
* **Unauthorized errors:** Have you specified your credentials correctly?

**Step 9.** Let's create the `/payments` request ([see docs](https://docs.adyen.com/online-payments/build-your-integration/advanced-flow/?platform=Web&integration=Drop-in&version=5.63.0&programming_language=java#post-payments-request-web)) on the backend.
**Step 9.** Let's create the `/payments` request in `/controllers/ApiController.java` ([see also docs](https://docs.adyen.com/online-payments/build-your-integration/advanced-flow/?platform=Web&integration=Drop-in&version=5.63.0&programming_language=java#post-payments-request-web)) on the backend.
We start by defining a new endpoint `/api/payments` to which our frontend will send a request.

<details>
Expand All @@ -331,7 +335,6 @@ We start by defining a new endpoint `/api/payments` to which our frontend will s
// The returnUrl field basically means: Once done with the payment, where should the application redirect you?
paymentRequest.setReturnUrl(request.getScheme() + "://" + host + "/api/handleShopperRedirect?orderRef=" + orderRef); // Example: Turns into http://localhost:8080/api/handleShopperRedirect?orderRef=354fa90e-0858-4d2f-92b9-717cb8e18173


log.info("PaymentsRequest {}", paymentRequest);
var response = paymentsApi.payments(paymentRequest);
log.info("PaymentsResponse {}", response);
Expand All @@ -343,8 +346,11 @@ We start by defining a new endpoint `/api/payments` to which our frontend will s



**Step 10.** Let's send a request to our backend from our frontend, and modify the `adyenWebImplementation.js` to override the `onSubmit(...)` function to send a request to the `/api/payments` endpoint.
We've added the `onSubmit(...)` event handler here and the `handleResponse(response, component)` function to handle the response (which is performing a simple redirect to the right page).
**Step 10.** Let's send a request to our backend from our frontend, and modify `adyenWebImplementation.js` to override the `onSubmit(...)` function to send a request to the `/api/payments` endpoint.
We've added **two things* to the existing functionality here:
* the `onSubmit(...)` event handler
* the `handleResponse(response, component)` function to handle the response (which is doing a simple redirect based on the response)


<details>
<summary>Click to show me the answer</summary>
Expand Down Expand Up @@ -422,6 +428,8 @@ Add the idempotency key to your payment request, see [documentation](https://doc
<details>
<summary>Click to show me the answer</summary>

You can add this to the existing code in the `/controllers/ApiController.java -> '/api/payments/'`-function

```java
var requestOptions = new RequestOptions();
requestOptions.setIdempotencyKey(UUID.randomUUID().toString());
Expand All @@ -434,7 +442,10 @@ Add the idempotency key to your payment request, see [documentation](https://doc

</details>

You should now be able to make a payment! **However**, we're not there yet! This flow will fail when a challenge is presented to the shopper (Strong Customer Authentication). Let's handle this by adding 3D Secure 2 Authentication support.
You should now be able to make a payment, visit the [documentation/test-card-page](https://docs.adyen.com/development-resources/testing/test-card-numbers/) and make a payment using one of the test cards. Alternatively, you can download the official [Adyen Test Card Extension](https://chromewebstore.google.com/detail/adyen-test-cards/icllkfleeahmemjgoibajcmeoehkeoag) to prefill your card numbers.


Congratulations! **However**, we're not there yet! This flow will fail when a challenge is presented to the shopper (Strong Customer Authentication). Let's handle this by adding 3D Secure 2 Authentication support.

3D Secure 2 is an authentication protocol (3DS2) that provides an additional layer of verification for card-not-present (CNP) transactions. To trigger 3DS2, we'll need to add several parameters to the `PaymentRequest` in the `/api/payments` endpoint.
Pick one of these two options.
Expand Down Expand Up @@ -507,20 +518,20 @@ Go back to the `/controller/ApiController`, let's add the following parameters t
log.info("PaymentDetailsRequest {}", detailsRequest);
var response = paymentsApi.paymentsDetails(detailsRequest);
log.info("PaymentDetailsResponse {}", response);
return ResponseEntity.ok()
.body(response);
return ResponseEntity.ok().body(response);
}
```

</details>

Next up, in our frontend, let's override the `onAdditionalDetails(...)` function in `adyenWebImplementation.js` to call `/api/payments/details`.
Next up, let's override the `onAdditionalDetails(...)` function in `adyenWebImplementation.js` to call `/api/payments/details`.


<details>
<summary>Click to show me the answer</summary>
<summary>Click to show me the answer </summary>

We've added the `onAdditionalDetails(...)` function in the `configuration` object and modified the `handleResponse(response, component)` function to allow the component to handle the challenge, see `component.handleAction(response.action)`.
Notice how we've only added two extra things here. The other parts of the code, should already be part of your application.

```js
// ...
Expand Down Expand Up @@ -553,7 +564,7 @@ async function startCheckout() {
handleResponse(response, component);
}
},
// Step 13 onAdditionalDetails(...), this function is executed when there's f.e. a Native 3DS2 flow
// [!] Step 13 onAdditionalDetails(...), this function is executed when there's f.e. a Native 3DS2 flow
onAdditionalDetails: async (state, component) => {
const response = await sendPostRequest("/api/payments/details", state.data);
handleResponse(response, component);
Expand All @@ -571,7 +582,7 @@ async function startCheckout() {

// Step 10 - Handles responses, do a simple redirect based on the result.
function handleResponse(response, component) {
// Step 13 - If there's an action, handle it, otherwise redirect the user to the correct page based on the resultCode.
// [!] Step 13 - If there's an action, handle it, otherwise redirect the user to the correct page based on the resultCode.
if (response.action) {
component.handleAction(response.action);
} else {
Expand Down Expand Up @@ -600,9 +611,10 @@ function handleResponse(response, component) {


**Step 14.** Let's handle 3DS2 in our `/payments/details`-request by passing the `redirectResult` or `payload` in the `/payments/details`-call.

Add the following function the `controllers/ApiController.java` class.

```java

// Step 14 - Handle Redirect 3DS2 during payment.
@GetMapping("/api/handleShopperRedirect")
public RedirectView redirect(@RequestParam(required = false) String payload, @RequestParam(required = false) String redirectResult) throws IOException, ApiException {
Expand Down Expand Up @@ -662,11 +674,10 @@ public RedirectView redirect(@RequestParam(required = false) String payload, @Re

**Step 16.** In order to receive payment updates. You need to configure webhooks in the Customer Area. The steps are quite straight forward.

You can receive webhooks by enabling webhooks in the Customer Area, followed by creating your `/webhooks`-endpoint in `Controllers/WebhookController.java`.
- [Read the documentation first: Enable and verify HMAC signatures](https://docs.adyen.com/development-resources/webhooks/verify-hmac-signatures/)
You can receive webhooks by enabling webhooks in the Customer Area, followed by creating your `/webhooks`-endpoint in `controllers/WebhookController.java`.
- [Read the documentation: Enable and verify HMAC signatures](https://docs.adyen.com/development-resources/webhooks/verify-hmac-signatures/)
- Create a standard webhook in your Customer Area. Example URL -> `https://xxxx-xx.gitpod.io/webhooks` or `https://xxxx.github.dev/webhooks`
- Don't forget to inject your `ADYEN_HMAC_KEY` in your `ApplicationConfiguration.java`, which you can then use to verify the HMAC signature.
- Create a new `WebhookController.java` in `/java/com/adyen/workshop/controllers/WebhookController.java`


<details>
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/adyen/workshop/MainApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +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) +
"\nApplication is running on http://localhost:" + (applicationConfiguration.getAdyenMerchantAccount() != null) +
"\nApplication is running on http://localhost:" + (applicationConfiguration.getAdyenClientKey() != null) +
"\n----------------------------------------------------------");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ public class ApplicationConfiguration {
@Value("${server.port}")
private int serverPort;

@Value("${ADYEN_API_KEY:#{null}}")
@Value("${ADYEN_API_KEY:#{null}}") // Don't edit @Value(...)
private String adyenApiKey;

@Value("${ADYEN_MERCHANT_ACCOUNT:#{null}}")
@Value("${ADYEN_MERCHANT_ACCOUNT:#{null}}") // Don't edit @Value(...)
private String adyenMerchantAccount;

@Value("${ADYEN_CLIENT_KEY:#{null}}")
@Value("${ADYEN_CLIENT_KEY:#{null}}") // Don't edit @Value(...)
private String adyenClientKey;

@Value("${ADYEN_HMAC_KEY:#{null}}")
@Value("${ADYEN_HMAC_KEY:#{null}}") // Don't edit @Value(...)
private String adyenHmacKey; // We'll cover this in step 16.

public int getServerPort() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,6 @@ public ResponseEntity<PaymentResponse> payments(@RequestHeader String host, @Req

@PostMapping("/api/payments/details")
public ResponseEntity<PaymentDetailsResponse> paymentsDetails(@RequestBody PaymentDetailsRequest detailsRequest) throws IOException, ApiException {
// Step 12
var pay = new PaymentRequest();
pay.setShopperInteraction(PaymentRequest.ShopperInteractionEnum.ECOMMERCE);
return null;
}

Expand Down
5 changes: 3 additions & 2 deletions src/main/resources/static/adyenWebImplementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ async function startCheckout() {
// Step 8
}

// Step 12 - Handles responses, do a simple redirect based on the result.
// Step 10 - Handles responses, do a simple redirect based on the result.
function handleResponse(response, component) {

// We'll leave this empty for now and fix this in step 10.
}


// This function sends a POST request to your specified URL,
// the `data`-parameters will be serialized as JSON in the body parameters.
async function sendPostRequest(url, data) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<div layout:fragment="content">
<div class='main-container'>
<div class="info">
<h1>Workshop: Build Your Adyen Payment Integration</h1>
<h1>Workshop: Build Your Own Adyen Payment Integration</h1>
<p>In this workshop, we'll go over a step-by-step guide to building your own payment integration with Adyen. We'll guide you through the steps needed to integrate with Adyen and make your first payment on TEST. This includes the credentials, configuration, API requests (using the Java Adyen library, /paymentMethods, /payments, /payments/details and 3DS2), error handling and webhooks.</p>
<p>Follow the steps in the <a href="https://github.com/adyen-examples/adyen-step-by-step-integration-workshop">README</a>.</p>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/templates/redirect.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
<div id="clientKey" class="hidden" th:text="${clientKey}"></div>

<!-- Adyen Component client code to terminate the session-->
<script type="text/javascript" src="/adyenImplementation.js"></script>
<script type="text/javascript" src="/adyenWebImplementation.js"></script>
</div>
</body>

0 comments on commit 20e8f04

Please sign in to comment.