Building a Travel Agency Website with the Rapyd Payment Gateway
Tags: ecommerce, payments, fintech, rapydtuts
Published: 2024-06-11 07:00:00

By **Marshall Chikari**

The [Rapyd Collect API](https://docs.rapyd.net/en/rapyd-collect-363484.html) simplifies online payments on your website while also handling payments from all over the world with different currencies and methods. Rapyd is an API first company that allows you to collect, hold, and disburse funds in various countries using local payment methods. 

In this tutorial, you'll use a travel agency website as an example to see just how easy it is to use this API. By the end of the tutorial, you'll know how to integrate an effective payment system that you can use in many other web development projects. You'll be using Python Flask for the backend, React.js for the frontend, and SQLite for the database, so you'll be well equipped to handle payments for any online venture.

## Prerequisites

Before you begin, make sure you have the following:

- [Python and Flask](https://flask.palletsprojects.com/en/2.3.x/)
- [React.js](https://react.dev/)
- [SQLite](https://www.sqlalchemy.org/) for the database
- [Rapyd Client Portal account](https://dashboard.rapyd.net/) for accessing the Collect API

## Setting Up the Project Structure

Let's start by setting up the project structure.

First, create a new project directory:

```bash
mkdir python-react-rapyd
cd python-react-rapyd
```

Initialize a virtual environment for Flask:

```bash
python -m venv venv
source venv/bin/activate  # On Windows, use `venv\Scripts\activate`
```

Virtual environments are essential for isolating Python dependencies used in different projects. By creating a virtual environment named `venv`, you ensure that the packages and dependencies required for our Flask backend won't interfere with other Python projects on the system. Activating the virtual environment with the source `venv/bin/activate` or `venv\Scripts\activate on Windows` ensures that any installed packages are contained within this environment.

Create a Flask project:

```bash
pip install flask flask-sqlalchemy flask-bcrypt PyJWT flask-cors requests
mkdir python-backend
touch python-backend/app.py
```

Flask is a lightweight and flexible Python web framework that you'll use to build the backend of the travel agency website. By installing Flask and creating a dedicated directory for the backend `python-backend`, you establish the foundation for your server-side logic. You'll place all Flask-related code and files within this directory. The **app.py** file created within `python-backend` will serve as the entry point for our Flask application, where you define routes, database models, and other server-side functionality.

## Adding User Registration and Login (Flask Backend)

You'll next enhance the Flask backend to include user registration and login functionality. Visit the [GitHub repository](https://github.com/Rapyd-Samples/Building-a-Travel-Agency-Website-with-the-Rapyd-Payment-Gateway-Python-Flask-and-React) for the complete code.

You need to define a user model in `python-backend/app.py` to represent user data. Add the following code within your Flask app:

```python
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(50), unique=True, nullable=False)
    password = db.Column(db.String(60), nullable=False)
```

You'll now create a new route for user registration. Add the following code to your **app.py** file to create a user registration endpoint:

```python
@app.route('/api/register', methods=['POST'])
def register():
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')

    if not username or not password:
        return jsonify({'message': 'Username and password are required'}), 400

    hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')

    new_user = User(username=username, password=hashed_password)
    db.session.add(new_user)

    try:
        db.session.commit()
        return jsonify({'message': 'User registered successfully'}), 201
    except Exception as e:
        db.session.rollback()
        if 'UNIQUE constraint failed' in str(e):
            return jsonify({'message': 'Username already exists'}), 400
        else:
            return jsonify({'message': 'An error occurred'}), 500

```

To create a user login endpoint, add this code to **app.py**:

```python
@app.route('/api/login', methods=['POST'])
def login():
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')

    if not username or not password:
        return jsonify({'message': 'Username and password are required'}), 400

    user = User.query.filter_by(username=username).first()

    if user and bcrypt.check_password_hash(user.password, password):
        token = generate_token(username)
        return jsonify({'user_id': user.id, 'token': token, 'message': 'Login successful'}), 200
    else:
        return jsonify({'message': 'Invalid username or password'}), 401

```

To run the Flask app, use the following command in your terminal while you're in the directory `python-backend`:

```bash
python app.py
```

This creates an instance of the database and the necessary tables, as you can see in the following screenshot:

![Travel app database](https://imgur.com/ZHBCbyz.png)

## Create React Frontend

Open your terminal and navigate to your project's root directory (`python-react-rapyd`).

Run the following command to create a new React application called `react-frontend`:

```js
npx create-react-app react-frontend
```

This command sets up a new React project with the default project structure.

### Remove Unnecessary Files

By default, `create-react-app` generates many files and folders that you might not need for this project. Let's clean up the project structure.

Navigate to the `react-frontend` directory, go into the `src` directory, and remove all files from the `components` directory except for **App.js**. Your `src` directory should now contain only the following files:

```bash
App.js
index.js
index.css
```

You'll next create the necessary components for login, registration, and trip listing. Inside the `src` directory, create a new folder called `components`:

```bash
mkdir src/components
```

Inside the `src/components` directory, add the following files to create the new components:

```bash
Login.js
Register.js
TripList.js
```

Now that you have the necessary components, update `src/App.js` to include routing for login, registration, and trip listing pages. You can use the `react-router-dom` library for this purpose.

Install `react-router-dom` by running the following command inside the `react-frontend` directory:

```bash
npm install react-router-dom
```

Update `src/App.js` to include routing for the components by adding the following code:

```jsx
import React from "react";
import { BrowserRouter as Router, Route, Redirect, Switch } from "react-router-dom";
import Login from "./components/Login";
import Register from "./components/Register";
import TripList from "./components/TripList";

function App() {

  return (
    <Router>
      <Switch>
        <Route exact path="/login">
          <Login />
        </Route>
        <Route exact path="/register">
          <Register />
        </Route>
        <Route exact path="/trips">
          <TripList />
        </Route>
        <Redirect to="/login" />
      </Switch>
    </Router>
  );
}

export default App;
```

Implement the login functionality in the **Login.js** component. This component will include a form where users can enter their username and password to log in. When the user submits the form, it will make an API request to the Flask backend for authentication.

Here's the code for the **Login.js** component:

```jsx
import React, { useState } from "react";
import { useCookies } from "react-cookie";
import axios from "axios";

function Login() {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const [cookies, setCookie] = useCookies(["token"]);
  const [loginError, setLoginError] = useState("");

  async function handleSubmit(event) {
    event.preventDefault();

    try {
      const response = await axios.post("http://localhost:5000/v1/login", {
        username,
        password,
      });

      const { token, user_id } = response.data;
      setCookie("token", token, { path: "/" });
      setCookie("user_id", user_id, { path: "/" });

      window.location.href = "/";
    } catch (error) {
      if (error.response && error.response.data.message) {
        setLoginError(error.response.data.message);
      } else {
        setLoginError("Login failed. Please try again.");
      }
    }
  }

  return (
    <div>
      <h2>Login</h2>
      {loginError && <p style={{ color: "red" }}>{loginError}</p>}{" "}
      <form onSubmit={handleSubmit}>
        <label>
          Username:
          <input
            type="text"
            value={username}
            onChange={(e) => setUsername(e.target.value)}
          />
        </label>
        <br />
        <label>
          Password:
          <input
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
          />
        </label>
        <br />
        <input type="submit" value="Submit" />
      </form>
    </div>
  );
}

export default Login;
```

In the code above, you use the `axios` library to make an API request to the Flask backend when the user submits the login form. In the `handleSubmit` function, you use **axios.post** to send a POST request to the Flask login endpoint (`http://localhost:5000/v1/login`). You then pass the username and password from the component's state as the request data. If the login is successful, you receive a response that typically includes a token and user ID, like in the image below:

![Successful login](https://imgur.com/V17S8rf.png)

To run the React application, run the following command in the directory `react-frontend`:

```bash
npm start
```

Here is a demonstration of the features of the base application, which involves creating a user account and making a booking.

![Demonstration of creating a booking](https://imgur.com/HQVZS7u.gif)

### Integrate the Hosted Checkout Page in Your App

Before you can use Rapyd's payment services, you need to obtain API keys.

Visit the [Rapyd Client Portal](https://dashboard.rapyd.net/) and retrieve your access keys by navigating to the **Developers** section:

![Rapyd page for developer keys](https://i.imgur.com/xxNQoG8.png)

## Set Up the Checkout Page

You can also update your checkout page to fit your brand. Just head over to the **Branding** section in the Rapyd Client Portal under **Settings > Branding**.

![Rapyd settings and branding page](https://i.imgur.com/wJuFbWl.png)

Here, you can pick the type of hosted page you want, like the hosted checkout, and add your company's logo to make it truly yours. You can even play with button colors, set up a fallback URL for a smoother user experience, and explore other branding options that fit your style. Don't forget to hit **Save** to put your changes into action. Whether you run a travel agency or any online business, these tweaks will help you craft a checkout experience that feels just right for your customers.

![Rapyd checkout page](https://i.imgur.com/aTGXypE.png)

### Implement Checkout in React

You'll now incorporate Rapyd Checkout into your travel agency website, allowing your users to securely make payments on a Rapyd hosted checkout page. Begin by integrating the provided code snippet into your **TripList.js** component:

```jsx
import React, { useState, useEffect } from "react";
import axios from "axios";
import { useCookies } from "react-cookie";

function TripList() {
  const [trips, setTrips] = useState([]);
  const [cookies] = useCookies(["token"]);
  const [bookingMessage, setBookingMessage] = useState(""); // State to track booking message

  useEffect(() => {
    const token = cookies.token;

    if (!token) {
      console.error("Token is missing");
      return;
    }

    // Fetch the list of trips when the component mounts
    axios
      .get("http://localhost:5000/v1/trips", {
        headers: {
          Authorization: token,
        },
      })
      .then((response) => {
        setTrips(response.data.trips);
      })
      .catch((error) => {
        console.error("Error fetching trips:", error);
      });
  }, [cookies.token]);

  const handleBookTrip = (tripId) => {
    const token = cookies.token;
    const userId = cookies.user_id;

    if (!token || !userId) {
      console.error("Token or user ID is missing");
      return;
    }

    // Make a POST request to your backend to initiate the Rapyd payment
    axios
      .post(
        "http://localhost:5000/v1/bookings",
        { trip_id: tripId, user_id: userId },
        {
          headers: {
            Authorization: token,
          },
        }
      )
      .then((response) => {
        if (response.status === 201) {
          if (
            response.data.payment_response &&
            response.data.payment_response.redirect_url
          ) {
            const redirectUrl = response.data.payment_response.redirect_url;
            // Redirect the user to the Rapyd hosted checkout page
            window.location.href = redirectUrl;
          } else {
            setBookingMessage("Redirect URL not provided in the response!");
          }
        } else {
          setBookingMessage("Booking failed");
        }
      })
      .catch((error) => {
        setBookingMessage("Error booking a trip");
        console.log("Error booking trip:", error);
      });
  };

  return (
    <div>
      <h2>Trip List</h2>
      <ul>
        {trips.map((trip) => (
          <li key={trip.id}>
            {trip.name} - ${trip.price} - {""}
            <button onClick={() => handleBookTrip(trip.id)}>Book</button>
          </li>
        ))}
      </ul>
      {bookingMessage && <p>{bookingMessage}</p>}{" "}
      {/* Display booking success message */}
    </div>
  );
}

export default TripList;
```

This component, **TripList.js**, uses the `useEffect` hook to fetch a list of trips from your backend API when it mounts. It uses the `axios` library to make a GET request to the `/v1/trips` endpoint, passing the authorization token in the header. The `handleBookTrip` function is called when the user clicks the **Book** button for a specific trip. It makes a `POST` request to your backend's `/v1/bookings` endpoint, passing the `trip_id` and `user_id` to initiate the booking process. When the `POST` request is successful (`HTTP status code 201`), it receives a response containing payment details, including the `redirect_url`. If the `redirect_url` is provided, the user is redirected to the Rapyd hosted checkout page.

If there's an error during the booking process, appropriate error messages are displayed using the `bookingMessage` state.

### Rapyd Utility Functions

The **rapyd_utils.py** file contains utility functions for interacting with the Rapyd API. These functions help with generating signatures and timestamps and making requests to Rapyd's API.

Here's a brief explanation of the key functions:

- `generate_salt`: generates a random string that is used as a salt
- `get_unix_time`: returns the current Unix timestamp
- `update_timestamp_salt_sig`: generates a signature for the API request based on the HTTP method, path, body, and API keys
- `current_sig_headers`: creates headers including access key, salt, timestamp, signature, and idempotency for the API request
- `pre_call`: prepares data (body, salt, timestamp, and signature) before making an API call
- `create_headers`: creates headers for the API request
- `make_request`: makes an HTTP request to the Rapyd API using the provided HTTP method, path, and body

These utility functions are used in your Flask backend to interact with the Rapyd API and initiate the payment process. Make sure to replace 'SECRET_KEY' and 'ACCESS_KEY' with your actual Rapyd API keys in the **rapyd_utils.py** file when you integrate it with your backend.

Here is a demo image of the integration:

![Demonstration of Rapyd integration](https://imgur.com/YQNNlGS.gif)

## Get the Code
Get the [**code**](https://github.com/Rapyd-Samples/Building-a-Travel-Agency-Website-with-the-Rapyd-Payment-Gateway-Python-Flask-and-React), build something amazing with the Rapyd API, and share it with us here in the developer community. Hit reply below if you have questions or comments.