Mar 27, 2023

/

701 Views

How to integrate YouCan Pay with React.js/Next.js

How to integrate YouCan Pay with React.js/Next.js

Nowadays making payments through a website is becoming ubiquitous, as such, there is a variety of payment gateways to choose from. But if you are living in morocco it’s pretty hard to find a suitable payment gateway that combines a nice user experience and a no legal issues.

There is a newly launched payment gateway in morocco called YouCanPay which has all the niceties that make it good to work with, in terms of developer experience, and how straightforward it is.

I will try to keep this blog post short and to the point.

Prerequisites

  • We will be working with Next.js because we are gonna utilize their API Routes functionality to generate a token(more on that later). So, first, pick up any example from the Next.js repository to play with this.
  • Because YouCanPay hasn’t introduced a Node.js library yet, a friend of mine Lynvi made a library which is a 1:1 map of the PHP YouCan Pay SDK. Trust me, it’s safe 🙂, and I’m using it in production(In which I will share a demo later at the end)

Install the SDK using your favorite package manager

yarn add youcan-payment-nodejs-sdk

How does the payment workflow in YouCan Pay look like

youcan-pay-flow-illustration.png

This is pretty much how most payment gateways like stripe work

To show the payment form, you have to generate a token containing informations about the order and the customer, etc...

Generating a token

We will create an API page in /pages/api/tokenize.ts

import type { NextApiRequest, NextApiResponse } from 'next'
import { CurrencyCode, YouCanPay } from 'youcan-payment-nodejs-sdk'

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
  try {
    const youCanPayment = new YouCanPay(
      process.env.YCP_PRI_KEY as string,
      process.env.NEXT_PUBLIC_YCP_IS_SANDBOX as any
    )
    const token = await youCanPayment.getToken({
      order_id: req.body.orderId,
      amount: req.body.price,
      currency: CurrencyCode.MAD,
      customer_ip: '127.0.0.1',
      success_url: 'https://domain.ma/payments/success',
      error_url: 'https://domain.ma/payments/error',
      customer: req.body.customer,
    })

    res.status(200).json({
      ok: true,
      data: { token: token.id },
      message: 'Token created',
    })
  } catch (error) {
    res.status(400).json({
      ok: false,
      data: null,
      message: 'Error creating token',
    })
  }
}

export default handler

So when a user submits a form containing all the necessary informations you need, like name, phone number, address, etc…

Then will immediately call the tokenize api above to generate the transaction token.

const [loading, setLoading] = useState(false)
const [payLoading, setPayLoading] = useState(false)
const [token, setToken] = React.useState<string>('')
const [shouldProceed, setShouldProcceed] = React.useState({
  proceed: false,
  orderId: '',
  customer: {
    name: '',
    address: '',
    phone: '',
    zip_code: '',
    city: '',
    state: '',
    country_code: '',
    email: '',
  },
})

const onSubmit = async (data: OrderType) => {
  setLoading(true)
  const { address, lastName, firstName, phoneNumber } = data
  try {
    // On sumbitting the form we create an order with the information provided, this createOrder function could be anything, like talking to a shopify API, or a custom solution
    const createdOrder = await createOrder({
      firstName,
      lastName,
      address,
      phone: phoneNumber,
      price: product.price.value,
    })
    if (createdOrder.ok) {
      // If the order was created successfully, we can create a token 
      //for that order with the corresponding id
      const res = await axios('/api/tokenize', {
        method: 'POST',
        data: {
          orderId: createdOrder.orderId,
          price: product.price.value * 100,
          customer: {
            name: `${firstName} ${lastName}`,
            address,
            phone: phoneNumber,
            country_code: 'MA',
            // i chose to provide empty values for the other fields
            // because I don't have any data to provide, they could
            // be anything else.
            state: 'emtpy',
            city: 'empty',
            email: 'empty',
            zip_code: 'empty',
          },
        },
      })
      if (res.data.ok) {
        // after the token is created, we can show the user the payment form,
        // here we will only update the state, because there a
        // useEffect comming that will handle showing the payment form
        // depending on the state provided bellow.
        setToken(res.data?.data?.token)
        setShouldProcceed({
          proceed: true,
          orderId: createdOrder.orderId,
          customer: {
            name: `${firstName} ${lastName}`,
            address,
            phone: phoneNumber,
            country_code: 'MA',
            state: 'emtpy',
            city: 'empty',
            email: 'empty',
            zip_code: 'empty',
          },
        })
        setLoading(false)
      } else {
        console.log(res.data.message)
        setLoading(false)
      }
    }
  } catch ({ errors }) {
    console.log('errors: ', errors)
    setLoading(false)
  }
}

Now we will have a useEffect that takes care of showing the payment form depending on the state we set above, and any payment events.

React.useEffect(() => {
  let ycPay: any = null
  if (typeof window !== 'undefined' && shouldProceed.proceed) {
    if (token?.length > 0) {
      //@ts-ignore
      ycPay = new YCPay(process.env.NEXT_PUBLIC_YCP_PUB_KEY, {
        locale: 'fr',
        isSandbox: process.env.NEXT_PUBLIC_YCP_IS_SANDBOX,
        errorContainer: '#error-container',
        formContainer: '#payment-container',
      })

      // render the payment methods
      ycPay.renderAvailableGateways([], 'default')

      if (document) {
        document
          ?.getElementById('pay')
          ?.addEventListener('click', function () {
            // Execute the payment, it is required to put the
            // created token in the tokenization step.
            setPayLoading(true)
            ycPay
              .pay(token)
              .then(async (payed: any) => {
                await onSubmitAfterPayment('Payé')
                setPayLoading(false)
                router.push(`/product/${router.query.slug}/order-complete`)
              })
              .catch((error: any) => {
                toastError(error?.errorMessage || 'An error occured')
                setPayLoading(false)
              })
          })
      }
    }
  }
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, [token, shouldProceed.proceed])

But how or where does YouCan Pay know where to put the payment form?

We can just put these HTML tags, wherever we need the form to be.

<div className={classNames(!shouldProceed.proceed ? 'hidden' : '')}>
  <div className="border-2 mt-2 p-4 h-full">
    <div className="">
        <div id="error-container"></div>
        <div id="payment-container"></div>
    </div>
  </div>
</div>

It’s important that we don’t conditionally render the components depending on the proceed state, otherwise useEffect will set some unexpected results.

That’s it, now you have a fully working YouCan Pay with Next.js

Demo

We are using YouCan Pay at https://emercado.ma as our payment gateway of choice.

You can test this functionality though with the sandbox mode here: https://staging.emercado.ma

Subscribe to my newsletter

You like what you just read?
Subscribe and get interesting software development content like this right in you inbox.