Jul 04, 2022

/

159 Views

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

notion image

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.

© 2022 Yassine Bridi