Building a Travel Agency Website with the Rapyd Payment Gateway

By Marshall Chikari

Getting Started

The Rapyd Collect API simplifies online payments on your website while also handling payments from all over the world with different currencies and 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:

Setting Up the Project Structure

Let’s start by setting up the project structure.

First, create a new project directory:

mkdir python-react-rapyd
cd python-react-rapyd

Initialize a virtual environment for Flask:

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:

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 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:

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:

@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:

@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:

python app.py

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

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:

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:

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:

mkdir src/components

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

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:

npm install react-router-dom

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

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:

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:

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

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

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 and retrieve your access keys by navigating to the Developers section:

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.

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.

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:

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

Get The Code

Get the code, 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.