👽

Next.jsでGMO PAYMENT GATEWAYのトークン決済を実装する

2022/01/29に公開

参考にさせていただいた記事
https://qiita.com/wanna_be_neet/items/9835212bea2f615bef2a#正解

環境

  • next:12.0.7
  • typescript:4.5.4

https://www.epsilon.jp/developer/spec01.html

適当にwindowを拡張する😎

resultCodecreditCardの返り値がイマイチなので、必要最低限の型(安全ではない)のみ
今回はresultCodeの返り値が'000'以外の場合は問答無用でエラーにするのでtokenObjectのオプショナルをなくしている😂

global.d.ts
declare global {
  interface Window {
    Multipayment: {
      getToken: (creditCard: {
        cardno: number
        expire: number // YYYYMM or YYMM
        holdername: string
        securitycode: number
      }) => {
        resultCode: `000` | number
        tokenObject: {
          isSecurityCodeSet: boolean | string
          maskedCardNo: string
          toBeExpiredAt: string
          token: string
        }
      }
      init: (apiKey: string) => void
    }
  }
}
const Multipayment = window.Multipayment

Next.jsのScriptを使いトークン取得のためのJavaScriptを読み込ませる👽

ステージングと本番でJavaScriptのURLが違うのでprocess.env.NEXT_PUBLIC_GMO_TOKEN_URLに切り出す
onLoadを使いJavaScriptが読み込まれたらMultipayment.initを呼び出す
ちなみにafterInteractiveは初期値なのでつけなくても良い😎
https://nextjs.org/docs/basic-features/script

GMOMultiPaymentScript.tsx
import Script from 'next/script'

/**
 * @see https://nextjs.org/docs/basic-features/script
 */

type GMOMultiPaymentScriptProps = Required<
  Readonly<{
    apiKey: string
  }>
>

export const GMOMultiPaymentScript = ({
  apiKey,
}: GMOMultiPaymentScriptProps) => (
  <Script
    src={process.env.NEXT_PUBLIC_GMO_TOKEN_URL}
    strategy={'afterInteractive'}
    onLoad={() => {
      Multipayment.init(apiKey)
    }}
  />
)

トークンを取得するhooksを作成する✨

Awaitedはいいぞ!

useGMOMultiPayment.ts
export const useGMOMultiPayment = () => {
    const getToken = (
      creditCard: Parameters<typeof Multipayment.getToken>[0]
    ) => {
      return new Promise<ReturnType<typeof Multipayment.getToken>>((resolve) => {
        Multipayment.getToken(creditCard, resolve)
      })
    }

    return {
      getToken,
    }
  }

type useGMOMultiPaymentType = typeof useGMOMultiPayment
export type useGMOMultiPaymentResult = ReturnType<useGMOMultiPaymentType>
export type useGMOMultiPaymentGetTokenResult = Awaited<
  ReturnType<useGMOMultiPaymentResult['getToken']>
  >

とりあえず、雑な実装

テスト専用のカード
https://mp-faq.gmo-pg.com/s/article/D00861

UIはchakra-uiをformはreact-hook-formを使っています。

import {
  useGMOMultiPayment,
  useGMOMultiPaymentGetTokenResult,
} from '@src/models/Payment/useGMOMultiPayment'
import { useForm } from 'react-hook-form'

export type GMOMultiPaymentInput = {
  cardno: number
  expireMonth: number
  expireYear: number
  holdername: string
  securitycode: number
}

export const useCreatePaymentMethod = ({
  onCompleted,
  onError,
}: Partial<{
  onCompleted: (data: useGMOMultiPaymentGetTokenResult) => void
  onError: () => void
}> = {}) => {
  const form = useForm<GMOMultiPaymentInput>()
  const { getToken } = useGMOMultiPayment()

  const onCreatePaymentMethod = form.handleSubmit(
    async ({ cardno, expireMonth, expireYear, holdername, securitycode }) => {
      const paddingMonth = String(expireMonth).padStart(2, '0')

      try {
        const data = await getToken({
          cardno,
          expire: `${expireYear}${paddingMonth}`,
          holdername,
          securitycode,
        })

        if (data.resultCode === '000') {
          console.log(data.tokenObject.token)
          onCompleted?.(data)
        } else {
          console.log('error')
          onError?.()
        }
      } catch (e) {
        console.log('error')
        onError?.()
      }
    }
  )

  return {
    form,
    onCreatePaymentMethod,
  }
}

type useCreatePaymentMethodType = typeof useCreatePaymentMethod
export type useCreatePaymentMethodResult = ReturnType<useCreatePaymentMethodType>
// export type useCreatePaymentMethodParameter = Parameters<useCreatePaymentMethodType>[0]

import {
  Button,
  chakra,
  Flex,
  FormControl,
  FormLabel,
  Input,
} from '@chakra-ui/react'
import {
  GMOMultiPaymentScript,
  useCreatePaymentMethod,
} from '@src/models/Payment'

export const CreatePaymentMethodForm = () => {
  const { form, onCreatePaymentMethod } = useCreatePaymentMethod()

  return (
    <chakra.form
      borderWidth={1}
      maxW={600}
      mx={'auto'}
      px={4}
      py={5}
      onSubmit={onCreatePaymentMethod}
    >
      <GMOMultiPaymentScript apiKey={'your api key'} />
      <FormControl>
        <FormLabel>カード番号</FormLabel>
        <Input
          type={'number'}
          {...form.register('cardno', { valueAsNumber: true })}
        />
      </FormControl>
      <Flex gap={2}>
        <FormControl>
          <FormLabel></FormLabel>
          <Input
            type={'number'}
            {...form.register('expireYear', { valueAsNumber: true })}
          />
        </FormControl>
        <FormControl>
          <FormLabel></FormLabel>
          <Input
            type={'number'}
            {...form.register('expireMonth', { valueAsNumber: true })}
          />
        </FormControl>
      </Flex>
      <FormControl>
        <FormLabel>名義人</FormLabel>
        <Input {...form.register('holdername')} />
      </FormControl>
      <FormControl>
        <FormLabel>セキュリティコード</FormLabel>
        <Input
          maxW={'6em'}
          textAlign={'right'}
          type={'number'}
          {...form.register('securitycode', { valueAsNumber: true })}
        />
      </FormControl>
      <Flex justify={'center'} mt={3}>
        <Button
          colorScheme={'teal'}
          minW={'10em'}
          type={'submit'}
          variant={'outline'}
        >
          送信
        </Button>
      </Flex>
    </chakra.form>
  )
}

下記の画像のようなレスポンスが返ってきてたら成功!
お疲れ様でした☕️

Discussion