DEV Community

Abdur Rakib Rony
Abdur Rakib Rony

Posted on

Implementing Hosted Checkout with ANZ Worldline Payment Solutions in Next.js

As developers, integrating secure and efficient payment solutions into our applications is crucial. In this post, we'll walk through implementing a hosted checkout solution using ANZ Worldline Payment Solutions in a Next.js application. This approach offers a seamless payment experience while maintaining high security standards.

What is Hosted Checkout?
Hosted Checkout is a payment solution where the payment form is hosted by the payment provider. This method simplifies PCI DSS compliance as sensitive card data is handled directly by the provider's secure systems.

Prerequisites

A Next.js application
An account with ANZ Worldline Payment Solutions
Basic knowledge of React and API routes in Next.js

Setting Up the Project
First, let's set up our Next.js project structure:

src/
  app/
    api/
      create-hosted-checkout/
        route.js
      payment-details/
        [paymentId]/
          route.js
    checkout/
      page.jsx
    payment-result/
      page.jsx
    page.jsx
Enter fullscreen mode Exit fullscreen mode

Implementing the Hosted Checkout

  1. Create the Hosted Checkout API Route In src/app/api/create-hosted-checkout/route.js:
import { NextResponse } from "next/server";
const onlinePaymentsSdk = require("onlinepayments-sdk-nodejs");

const client = onlinePaymentsSdk.init({
    host: 'payment.preprod.anzworldline-solutions.com.au',
    apiKeyId: 'YOUR_API_KEY_ID',
    secretApiKey: 'YOUR_SECRET_API_KEY',
    integrator: 'OnlinePayments',
});

const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000';

export async function POST(request) {
    const createHostedCheckoutRequest = {
        order: {
            amountOfMoney: {
                amount: 10 * 100, // 1000 cents AUD = $10
                currencyCode: 'AUD',
            },
        },
        hostedCheckoutSpecificInput: {
            returnUrl: `${baseUrl}/payment-result`,
        },
    };

    try {
        const response = await client.hostedCheckout.createHostedCheckout(
            'YOUR_MERCHANT_ID',
            createHostedCheckoutRequest,
            null
        );

        return NextResponse.json(response);
    } catch (error) {
        console.error("Error creating payment:", error);
        return NextResponse.json(
            { error: "Failed to create payment", details: error.message },
            { status: 500 }
        );
    }
}
Enter fullscreen mode Exit fullscreen mode
  1. Implement the Checkout Page In src/app/checkout/page.jsx:
"use client";
import React, { useState, useEffect } from "react";
import { CreditCard } from "lucide-react";

export default function CheckoutPage() {
    const [res, setRes] = useState(null);

    useEffect(() => {
        if (res && res.body && res.body.redirectUrl) {
            window.location.href = res.body.redirectUrl;
        }
    }, [res]);

    const submitForm = async () => {
        try {
            const response = await fetch("/api/create-hosted-checkout", {
                method: "POST",
            });
            const data = await response.json();
            setRes(data);
        } catch (error) {
            console.error("Error during checkout:", error);
        }
    };

    return (
        <div className="min-h-screen py-6 flex flex-col justify-center items-center container mx-auto">
            <button
                className="border flex items-center rounded-lg p-2 bg-fuchsia-500 font-semibold text-white hover:bg-fuchsia-600 transition-colors"
                onClick={submitForm}
            >
                <CreditCard className="mr-2 h-5 w-5" /> Checkout
            </button>
            {res && (
                <div className="border rounded-lg p-10 mt-10 break-words max-w-full overflow-x-auto">
                    <p>Redirecting to payment page...</p>
                    <pre className="mt-4 text-sm">
                        {JSON.stringify(res, null, 2)}
                    </pre>
                </div>
            )}
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

3. Handle Payment Result
Create an API route to fetch payment details:
In src/app/api/payment-details/[paymentId]/route.js:

import { NextResponse } from "next/server";
const onlinePaymentsSdk = require("onlinepayments-sdk-nodejs");

const client = onlinePaymentsSdk.init({
    host: 'payment.preprod.anzworldline-solutions.com.au',
    apiKeyId: 'YOUR_API_KEY_ID',
    secretApiKey: 'YOUR_SECRET_API_KEY',
    integrator: 'OnlinePayments',
});

export async function GET(request, { params }) {
    const hostedCheckoutId = params.paymentId;

    if (!hostedCheckoutId) {
        return NextResponse.json(
            { error: "Hosted Checkout ID is required" },
            { status: 400 }
        );
    }

    try {
        const hostedCheckoutStatus = await client.hostedCheckout.getHostedCheckout(
            'YOUR_MERCHANT_ID',
            hostedCheckoutId,
            null
        );

        return NextResponse.json(hostedCheckoutStatus);
    } catch (error) {
        console.error("Error getting payment details:", error);
        return NextResponse.json(
            { error: "Failed to get payment details", details: error.message },
            { status: 500 }
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

Then, create a page to display the payment result:
In src/app/payment-result/page.jsx:

"use client";
import React, { useEffect, useState } from "react";
import { useSearchParams } from "next/navigation";

export default function PaymentResultPage() {
    const [paymentDetails, setPaymentDetails] = useState(null);
    const [error, setError] = useState(null);
    const searchParams = useSearchParams();
    const hostedCheckoutId = searchParams.get("hostedCheckoutId");

    useEffect(() => {
        if (hostedCheckoutId) {
            fetch(`/api/payment-details/${hostedCheckoutId}`)
                .then((response) => {
                    if (!response.ok) {
                        throw new Error('Network response was not ok');
                    }
                    return response.json();
                })
                .then((data) => setPaymentDetails(data))
                .catch((error) => {
                    console.error("Error fetching payment details:", error);
                    setError("Failed to fetch payment details. Please try again.");
                });
        }
    }, [hostedCheckoutId]);

    if (error) {
        return <div className="text-red-500">{error}</div>;
    }

    if (!paymentDetails) {
        return <div>Loading payment details...</div>;
    }

    return (
        <div className="p-4">
            <h1 className="text-2xl font-bold mb-4">Payment Result</h1>
            <pre className="bg-gray-100 p-4 rounded overflow-x-auto">
                {JSON.stringify(paymentDetails, null, 2)}
            </pre>
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

Conclusion
With these components in place, you've successfully implemented a hosted checkout solution using ANZ Worldline Payment Solutions in your Next.js application. This approach provides a secure and seamless payment experience for your users while simplifying PCI DSS compliance for your application.
Remember to replace placeholder values like YOUR_API_KEY_ID, YOUR_SECRET_API_KEY, and YOUR_MERCHANT_ID with your actual credentials from ANZ Worldline Payment Solutions.
By leveraging hosted checkout, you can focus on building great features for your application while leaving the complexities of payment processing to the experts.

Top comments (0)