Open10

PayPay 決済を Next.js で実験

9sako69sako6

必要なクレデンシャル

.env.local
PAYPAY_API_KEY=XXXXX
PAYPAY_SECRET=XXXXX
PAYPAY_MERCHANT_ID=1234567890
9sako69sako6

まずは PayPay で支払う画面の URL を生成する必要がある。

src/app/api/paypay/route.ts
import { Configure, QRCodeCreate } from '@paypayopa/paypayopa-sdk-node';
import { NextResponse } from 'next/server';
import { v4 as uuidv4 } from 'uuid';
import { z } from 'zod';

Configure({
  clientId: process.env.PAYPAY_API_KEY || '',
  clientSecret: process.env.PAYPAY_SECRET || '',
  merchantId: process.env.PAYPAY_MERCHANT_ID || '',
  productionMode: false,
})

const PayPaySuccessResponse = z.object({
  STATUS: z.literal(201),
  BODY: z.object({
    data: z.object({
      url: z.string(),
    }),
  }),
})

export async function POST() {
  const merchantPaymentId = uuidv4();

  const payload = {
    merchantPaymentId,
    amount: {
      amount: 300,
      currency: "JPY",
    },
    codeType: 'ORDER_QR',
    orderDescription: 'Test Order',
    isAuthorization: false,
    redirectUrl: `http://localhost:3000/payment/paypay/${merchantPaymentId}`,
    redirectType: 'WEB_LINK',
  }

  const res = await QRCodeCreate(payload)

  if (res.STATUS === 201) {
    const url = PayPaySuccessResponse.parse(res).BODY.data.url

    return NextResponse.json({
      url,
    })
  } else {
    return NextResponse.error()
  }
}

9sako69sako6
src/app/page.tsx
"use client";
import styles from "./page.module.css";

export default function Home() {
  const handlePayPayPayment = async () => {
    const res = await fetch("/api/paypay", {
      method: "POST",
    })
    const json = await res.json()

    window.location.href = json.url
  }

  return (
    <div className={styles.page}>
      <main className={styles.main}>
        <h1>
          PayPay 決済テスト
        </h1>

        <button onClick={handlePayPayPayment}>
          支払う
        </button>
      </main>
    </div>
  );
}

9sako69sako6

ちなみに、制限時間以内に支払いがなされなかった場合も redirectUrl に遷移する。

9sako69sako6

アカウントリンクの例。redirectUrl はダッシュボードで設定したドメインである必要がある。

import { AccountLinkQRCodeCreate, Configure } from '@paypayopa/paypayopa-sdk-node';
import { NextResponse } from 'next/server';
import { v4 as uuidv4 } from 'uuid';

Configure({
  clientId: process.env.PAYPAY_API_KEY || '',
  clientSecret: process.env.PAYPAY_SECRET || '',
  merchantId: process.env.PAYPAY_MERCHANT_ID || '',
  productionMode: false,
})

export async function POST() {
  const nonce = uuidv4();

  const payload = {
    redirectUrl: 'http://localhost:3000',
    redirectType: 'WEB_LINK',
    scopes: [
      "direct_debit"
    ],
    nonce,
  }

  const res = await AccountLinkQRCodeCreate(payload)

  return NextResponse.json(res)
}

返却される URL にアクセスして PayPay ログインすると、redirectUrl に飛ばされる。この際、responseToken クエリパラメータにある JWT にアカウント情報が含まれて帰ってくる。

{
  STATUS: 201,
  BODY: {
    resultInfo: { code: 'SUCCESS', message: 'Success', codeId: '08100001' },
    data: {
      linkQRCodeURL: 'https://stg-www.sandbox.paypay.ne.jp/app/opa/web/link?code=https%3A%2F%2Fqr-stg.sandbox.paypay.ne.jp%2F281801074UK0O9zI3ecYainF'
    }
  }
}

JWT のペイロード。userAuthorizationId を使うとユーザーに送金可能っぽい。

{
  "result": "succeeded",
  "aud": "XXXXXXXX",
  "iss": "paypay.ne.jp",
  "profileIdentifier": "*******0164",
  "exp": 1730340440,
  "nonce": "d84f3aa9-205b-4607-9b9f-603db20b67db",
  "userAuthorizationId": "87f534c2-bbdf-4da1-8300-19e418950642",
  "referenceId": null
}