Skip to content

Commit

Permalink
Init commit
Browse files Browse the repository at this point in the history
  • Loading branch information
kkonovalovXsolla committed Oct 25, 2024
0 parents commit 56c25c1
Show file tree
Hide file tree
Showing 13 changed files with 526 additions and 0 deletions.
14 changes: 14 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
XSOLLA_PROJECT_ID="YOUR_DATA"

# https://publisher.xsolla.com/<YOUR_MERCHANT>/projects/<YOUR_PROJECT>/edit/api_key
XSOLLA_API_KEY="YOUR_DATA"

# https://publisher.xsolla.com/<YOUR_MERCHANT>/projects/<YOUR_PROJECT>/edit/webhooks/
XSOLLA_WEBHOOK_SECRET_KEY="YOUR_DATA"

BACKEND_PORT=8080

# https://ngrok.com/ - for expose your local app to the internet
NGROK_AUTH_TOKEN="YOUR_DATA"
# ngrok web interface port
NGROK_PORT=4040
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.idea/
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.env

xsolla-example.war

# Avoid ignoring Maven wrapper jar file (.jar files are usually ignored)
!/.mvn/wrapper/maven-wrapper.jar
11 changes: 11 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Use Tomcat base image with Java 11
FROM tomcat:9.0-jdk11-openjdk-slim

# Install curl, wget, and unzip
RUN apt-get update && apt-get install -y curl wget unzip

# Add ngrok repository and install ngrok
RUN curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc | tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null \
&& echo "deb https://ngrok-agent.s3.amazonaws.com buster main" | tee /etc/apt/sources.list.d/ngrok.list \
&& apt-get update \
&& apt-get install -y ngrok
73 changes: 73 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Xsolla Client-Side Integration Example

This repository offers an example of integrating Xsolla PayStation for payment processing using Maven, Java, Servlets,
Tomcat, and Ngrok.

## Prerequisites

Ensure the following tools are installed on your system before proceeding:

- [Docker](https://docs.docker.com/get-docker/)
- [Docker Compose](https://docs.docker.com/compose/install/)
- [Ngrok](https://ngrok.com/)

## Setup Instructions

### 1. Create the .env file

Copy the .env.example file and rename it to .env:

```bash
cp .env.example .env
```

Update the .env file with the appropriate values. Your project ID is available in your Xsolla Publisher Account.

![screenshot](doc/img/where-id.png)

### 2. Build the project with Maven

```markdown
mvn clean install
```

The output xsolla-example.war file will be in the target directory.

**It's crucial to remember that your project needs to be rebuilt whenever changes are made to the codebase or .env variables.**

### 3. Start Docker Containers

Run the project using Docker Compose:

```bash
docker-compose up --build
```

The application will be accessible at http://localhost:8080/xsolla-example/server-side.

**It's crucial to remember that your Docker need to be rebuilt too after every project rebuilt.**

### 4. Configure Ngrok

![screenshot](doc/img/ngrok-url.png)
Ngrok provides a public URL for accessing your local server. You can open the Ngrok dashboard at http://localhost:4040.

Copy the resulting URL and append /xsolla-example/webhook to it (for example, https://<random>.ngrok-free.app/xsolla-example/webhook) and add it to the webhook settings of your project at https://publisher.xsolla.com/<YOUR_MERCHANT>/projects/<YOUR_PROJECT>/edit/webhooks/.

### 5. Add a product in Publisher Account

Create a product in your Xsolla Publisher Account. Navigate to your project > store > virtual items > add item, or open
the page directly at https://publisher.xsolla.com/<YOUR_MERCHANT>/projects/<YOUR_PROJECT>/storefront/virtual-items.

### 6. Test the payment process

Open the example UI at `http://localhost:8080/xsolla-example/server-side` and modify the JSON data to get a token (set
the SKU of the project, user ID, etc.).

Payment process:

1. Server: Create a token using your JSON data.
2. Client: Open the payment page with your token.
3. Server: Receive a webhook to validate the user.
4. Client: Complete the payment.
5. Server: Receive a webhook with the payment result and deliver the product to the customer.
Binary file added doc/img/ngrok-url.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/img/where-id.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
version: '3'
services:
app:
build: .
container_name: xsolla-example-server-side
ports:
- "${BACKEND_PORT}:8080"
volumes:
- ./target/:/usr/local/tomcat/webapps/
command: [ "catalina.sh", "run" ]
networks:
- app-network

ngrok:
image: wernight/ngrok
container_name: ngrok-xsolla-example-server-side
environment:
- NGROK_AUTHTOKEN=${NGROK_AUTH_TOKEN}
- NGROK_PORT=app:8080
ports:
- "${NGROK_PORT}:4040"
networks:
- app-network

networks:
app-network:
driver: bridge
2 changes: 2 additions & 0 deletions ngrok.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
version: 2
web_addr: 0.0.0.0:4040
99 changes: 99 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>xsolla</groupId>
<artifactId>xsolla-example</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>Xsolla Example Java</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>

<!-- dotenv-java -->
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-java</artifactId>
<version>3.0.0</version>
</dependency>

<!-- JSON processing -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20231013</version>
</dependency>

<!-- Apache Commons Codec for SHA-1 hashing -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>

<!-- JSTL dependency -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>

<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.9</version>
</dependency>
</dependencies>


<build>
<finalName>xsolla-example</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<path>/</path>
<port>8081</port>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.1</version>
</plugin>
</plugins>
<resources>
<resource>
<directory>${PWD}</directory>
<includes>
<include>.env</include>
</includes>
</resource>
</resources>
</build>
</project>
115 changes: 115 additions & 0 deletions src/main/java/xsolla/ServerSide.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package xsolla;

import io.github.cdimascio.dotenv.Dotenv;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.Base64;

@WebServlet("/server-side")
public class ServerSide extends HttpServlet {
private final String projectId;
private final String apiKey;
private final String backendPort;
private final String webhookSecretKey;

public ServerSide() {
Dotenv dotenv = Dotenv.load();
this.projectId = dotenv.get("XSOLLA_PROJECT_ID");
this.apiKey = dotenv.get("XSOLLA_API_KEY");
this.backendPort = dotenv.get("BACKEND_PORT");
this.webhookSecretKey = dotenv.get("XSOLLA_WEBHOOK_SECRET_KEY");
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setAttribute("xsollaProjectId", this.projectId);
request.setAttribute("xsollaApiKey", this.apiKey);
request.setAttribute("backendPort", this.backendPort);
request.setAttribute("xsollaWebhookSecretKey", this.webhookSecretKey);

request.getRequestDispatcher("/server-side.jsp").forward(request, response);
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String projectId = request.getParameter("projectId");
String apiKey = request.getParameter("apiKey");
String body = request.getParameter("body");

try {
new com.google.gson.JsonParser().parse(body);
} catch (Exception e) {
request.setAttribute("apiResponse", "Json in body is not valid");
request.getRequestDispatcher("/server-side.jsp").forward(request, response);
return;
}

try {
String urlString = "https://store.xsolla.com/api/v3/project/" + projectId + "/admin/payment/token";
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();

String auth = Base64.getEncoder().encodeToString((projectId + ":" + apiKey).getBytes());
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Authorization", "Basic " + auth);
conn.setDoOutput(true);

try (OutputStream os = conn.getOutputStream()) {
byte[] input = body.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}

StringBuilder apiResponse = new StringBuilder();
try (Scanner scanner = new Scanner(conn.getInputStream(), StandardCharsets.UTF_8.name())) {
while (scanner.hasNextLine()) {
apiResponse.append(scanner.nextLine());
}
}

String responseBody = apiResponse.toString();
String token = extractToken(responseBody);

boolean isSandbox = body.contains("\"sandbox\": true");
String baseUrl = isSandbox ? "https://sandbox-secure.xsolla.com/paystation4/?token=" : "https://secure.xsolla.com/paystation4/?token=";
String fullUrl = baseUrl + token;

String formattedLink = "<a href=\"" + fullUrl + "\" target=\"_blank\">" + fullUrl + "</a>";
String completeResponse = "<h3>API Response:</h3><pre>" + escapeHtml(responseBody) + "</pre>" + "<h3>Payment Link:</h3>" + formattedLink;

request.setAttribute("apiResponse", completeResponse);
request.setAttribute("body", body);
request.setAttribute("xsollaProjectId", this.projectId);
request.setAttribute("xsollaApiKey", this.apiKey);
request.setAttribute("xsollaWebhookSecretKey", this.webhookSecretKey);

} catch (Exception e) {
request.setAttribute("apiResponse", "Error processing request: " + e.getMessage());
}

request.getRequestDispatcher("/server-side.jsp").forward(request, response);
}

private String extractToken(String json) {
int tokenStart = json.indexOf("\"token\":\"") + 9;
int tokenEnd = json.indexOf("\"", tokenStart);
return json.substring(tokenStart, tokenEnd);
}

private String escapeHtml(String input) {
return input.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&#39;");
}
}
Loading

0 comments on commit 56c25c1

Please sign in to comment.