npm package: @questps/react-upp-js
Description: Quest React payment library for UPP
Author: Quest Payment Systems
Latest version: 1.3.0

# React Unified Payment Platform (UPP) components

This project contains React components for UPP.

## Installation

To install this component run `npm i @questps/react-upp-js`.

## Test cards

The following card numbers are not real, they are testing only. Use these numbers with any CCV and any expiry in the future.

Mastercard approved 5123 4567 8901 2346

Visa approved 4005 5500 0000 0001

Visa declined 4557 0123 4567 8902

## Documentation

The payment component can be found at: https://questps.github.io/react-upp-js/ .

## Quick example

```tsx
"use client";

import "@questps/react-upp-js/css/creditCardToken.css";
import { useRef, useState } from "react";

export default function Home() {
  const [error, setError] = useState<string>();
  const getToken = useRef<CreditCardTokenCallback>();

  const submitToken = async () => {
    setError(undefined);
    const creditCardToken = await getToken.current!();
    // Perform the payment
  };

  return (
    <main>
      <div>
        <CreditCardToken
          uppPublicToken="your UPP token"
          environment="sandbox"
          getToken={i => {
            getToken.current = i;
          }}
          handleInvalid={setError}
          handleSubmit={submitToken}
          singleUse={true}
          autoFocus
        />
      </div>
      {error && <div style={{ color: "red" }}>{error}</div>}
      <div className="actions">
        <button type="button" onClick={submitToken}>
          Pay
        </button>
      </div>
    </main>
  );
}
```

## Full example

![Demo animation](docs/example-animation.gif)

This code is a complete end to end example - (it contains the server code and the widget). This example can be found in the "/example" folder of this repository.

1. Create a React application `npx create-next-app example`
1. Change the file "app/globals.css" to the following:

   ```css
   main {
     display: flex;
     flex-direction: column;
     gap: 1rem;
     margin: 0 auto;
     width: 100%;
     max-width: 500px;
     text-align: center;
   }

   button {
     background: #007bff;
     color: white;
     border: none;
     padding: 10px 20px;
     border-radius: 5px;
     cursor: pointer;
   }

   .actions {
     text-align: center;
   }

   .paymentResult {
     margin: auto 10rem;
     padding: 2rem;
     text-align: center;
   }

   .paymentResult.approved {
     border: 1px solid green;
   }

   .paymentResult.declined {
     border: 1px solid red;
   }
   ```

1. Add a new file "app/server.ts" with the following:

   ```ts
   "use server";

   import { headers } from "next/headers";

   const UPP_API_URL = process.env.UPP_API_URL!;
   const UPP_PRIVATE_TOKEN = process.env.UPP_PRIVATE_TOKEN!;

   async function uppFetch(url: string, options: RequestInit & { json?: any }) {
     const newOptions = { ...options };
     if (options.json) newOptions.body = JSON.stringify(options.json);

     const result = await fetch(`${UPP_API_URL}${url}`, {
       headers: {
         Authorization: `Bearer ${UPP_PRIVATE_TOKEN}`,
         "Content-Type": "application/json",
       },
       body: JSON.stringify(options.json),
       ...options,
     });

     const body = await result.json();
     return { ok: result.ok, body };
   }

   export async function payWithServer(customerToken: string): Promise<any> {
     const formattedAddress = {
       firstName: "John",
       lastName: "Smith",
       line1: "123 Swanston Street",
       city: "Melbourne",
       state: "VIC",
       postCode: "3000",
       countryCode: "au",
     };

     // This should be a UUID but to avoid a dependancy in example source we are using random
     const transactionReference = Math.floor(Math.random() * 1e10).toString();

     const authResult = await uppFetch("/v1/transactions", {
       json: {
         transactionType: "purchase",
         amount: 1,
         currency: "aud",
         // This assumes a reverse proxy is used to forward the client's IP address
         clientIpAddress: headers().get("x-forwarded-for"),
         customerToken,
         transactionReference,
         provider: "financialCard",
         saleType: "website",
         items: [
           {
             type: "sku",
             itemReference: "1",
             name: "Flowers",
             amount: 1,
             quantity: 1,
           },
         ],
         billing: formattedAddress,
         shipping: {
           isPickup: false,
           address: formattedAddress,
         },
         shopper: {
           firstName: "John",
           lastName: "Smith",
         },
       },
       method: "POST",
     });

     if (authResult.body.approved !== undefined) return authResult.body;

     const completeResult = await uppFetch(`/v1/transactions/${transactionReference}/complete`, {
       method: "POST",
     });
     return completeResult.body;
   }
   ```

1. Change the file "app/page.tsx" to the following:

   ```tsx
   "use client";

   import { CreditCardToken, CreditCardTokenCallback } from "@questps/react-upp-js";
   import "@questps/react-upp-js/css/creditCardToken.css";
   import { useRef, useState } from "react";
   import { payWithServer } from "./server";

   export default function Home() {
     const [error, setError] = useState<string>();
     const [paymentResult, setPaymentResult] = useState<any>();
     const getToken = useRef<CreditCardTokenCallback>();

     // This function is called when the user clicks the submit button or presses enter on the credit card token form
     const submitToken = async () => {
       setError(undefined);
       const creditCardToken = await getToken.current!();
       setPaymentResult(await payWithServer(creditCardToken));
     };

     if (paymentResult) {
       const isApproved = paymentResult.approved === true;
       return (
         <div className={`paymentResult ${isApproved ? "approved" : "declined"}`}>
           Your transaction was {isApproved ? "APPROVED" : "DECLINED"}
         </div>
       );
     }

     return (
       <main>
         <div>
           <CreditCardToken
             uppPublicToken={process.env.NEXT_PUBLIC_UPP_TOKEN!}
             environment="sandbox"
             getToken={i => {
               getToken.current = i;
             }}
             handleInvalid={setError}
             handleSubmit={submitToken}
             singleUse={true}
             autoFocus
           />
         </div>
         {error && <div style={{ color: "red" }}>{error}</div>}
         <div className="actions">
           <button type="button" onClick={submitToken}>
             Pay
           </button>
         </div>
       </main>
     );
   }
   ```

1. Create a ".env" file with the following contents (replacing anything between &lt; &gt; symbols)

   ```
   NEXT_PUBLIC_UPP_TOKEN=<you public token>
   UPP_API_URL=<the UPP host (either: https://payments.sandbox.upp.qps.io or https://payments.upp.qps.io)>
   UPP_PRIVATE_TOKEN=<your private token>
   ```