【入門】PayPayOPA（node-sdk） × Vue 3 × Docker で PayPay支払いをしてみよう

## はじめに
**・Vue.js、node.js を勉強する環境を作りたい！**
**・Docker(DockerCompose)を試してみたい！**
**・PayPayのAPIを使ってみたい！**
という方はぜひご覧ください。

私も半分以上ChatGPTに教えてもらったので、**やる気**さえあれば誰でもできます！
この記事で環境を作り、そこからご自身の学習に繋げてもらえると嬉しいです！

## 技術スタック
- フロントエンド
    - Vue 3（Vite）
    - Pinia（状態管理）
- バックエンド
    - Node.js + Express
    - PayPayOPA（node-SDK）
- 開発環境
    - Docker
    - Docker Compose

## ディレクトリ構成
```
📁 paypay-test-site/
├── 📁 frontend/                    # Vue 3 + Vite アプリ
│   ├── Dockerfile
│   ├── package.json
│   └── 📁 src/
│       ├── App.vue                # ルートコンポーネント
│       ├── main.js                # エントリーポイント
│       └── 📁 components/
│           └── CreateCode.vue     # PayPay API 呼び出し用ボタンなど
├── 📁 backend/            　　     # Express サーバー（API）
│   ├── Dockerfile
│   ├── package.json
│   ├── server.js
│   └── .env                       # PayPay API 用の認証情報
├── docker-compose.yml             # 開発環境の起動管理

```

## Docker Composeとは
Docker と Docker Compose についてざっくりわかりやすく書かれているので一通り見た方が良いです！
https://qiita.com/gon0821/items/77369def082745d19c38#%EF%BC%91-docker-compose%E3%81%A8%E3%81%AF

## PayPay OPA SDK を利用する準備
PayPayのAPI（OpenPaymentApi(OPA)）は検証環境であれば誰でも試すことができます。
以下の PayPay for Developers のサイトでメールアドレスとパスワード入力してアカウントを作成しましょう。
https://developer.paypay.ne.jp/account/signup

※ 初回ログイン時は加盟店IDやAPIキーが表示されないこともあります。一度ログアウトして、少し時間をおいて再ログインしてみてください。
※ スコープは後から追加できないので、とりあえずすべて選んでおくことをおすすめします。

## 1. Docker起動&フロントエンド作成
任意のディレクトリで作業ディレクトリを作成
```zsh
mkdir test-site && cd test-site && pwd
```

#### docker-compose.ymlの作成(`test-site/docker-compose.yml`)
```yml
services:
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    volumes:
      - ./frontend:/app
      - /app/node_modules
    command: npm run serve
```

#### frontendディレクトリ作成&移動(`test-site/frontend`)
```zsh
mkdir frontend && cd frontend && pwd
```

#### Dockerfile作成(`test-site/frontend/Dockerfile`)
```Dockerfile
FROM node:23
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8080
CMD ["npm", "run", "serve"]
```

#### Vueプロジェクトを作成&各モジュールをインストール
```zsh
# 実行前に test-site/frontend をVSコード等で開いておくと、何が起きているかわかりやすい！
npm install -g @vue/cli
vue create .
npm install axios pinia 
```

#### Dockerコンテナ起動
```zsh
docker compose up --build
```

#### ブラウザで起動確認（Vueのスタートページが表示される）
http://localhost:8080/

#### ここまでのよくあるエラー
Q1. dockerがない
A1. [Docker Desktop for Mac (Apple Silicon)](https://chatgpt.com/c/6828dd37-2fbc-8003-a495-a4b5ceeb2516#:~:text=Docker%20Desktop%20for%20Mac%20(Apple%20Silicon))をインストールする

Q2. brew、node、npmがない
A2. 以下でインストール
```bash
https://brew.sh/ja/
brew -v

brew install node
node -v
npm -v
```

#### よく使うdockerコマンド
参考: https://qiita.com/gon0821/items/77369def082745d19c38
```bash
# Dockerコンテナ起動
docker compose up --build

# Dockerコンテナをバックグラウンドで起動
docker compose up -d

# Dockerコンテナの一覧を表示
docker compose ps

# Dockerコンテナ停止・削除
docker compose down
```

#### TOPIC: ローカルの`test-site/frontend/src/App.vue`を修正してみよう
```bash
# まだDockerコンテナ起動中であれば停止
Ctrl+c

# Dockerコンテナをバックグラウンドで起動
docker compose up -d

# ブラウザで起動確認（まだVueのスタートページが表示される）
http://localhost:8080/

# ローカルの test-site/frontend/src/App.vue を以下のように修正
# 上で開いてるブラウザ画面の表示が変わっているはず！
<template>
  <div id="app">
    <h1>Hello, PayPay Test Site!</h1>
  </div>
</template>
```

## 2. バックエンド作成&PayPayのnode-sdkを試してみる
#### `test-site/frontend/src/components/CreateCode.vue` を新規作成して、以下を貼り付け
```vuejs
<template>
  <div class="p-4 border rounded">
    <input
      v-model="amount"
      type="text"
      placeholder="決済金額を入力"
      class="border p-2 mb-2 w-full"
    />
    <button @click="createCode">
      PayPayで支払う
    </button>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import axios from 'axios';
// import { useGlovalStore } from '@/stores/gloval'

const amount = ref('');
// const glovalStore = useGlovalStore()

const createCode = async () => {
  try {
    // テスト用のバックエンドエンドポイントにリクエスト（後で作成）
    const response = await axios.post(`http://localhost:3000/api/paypay/v2/codes/${amount.value}`);
    // glovalStore.resultLog = JSON.stringify(response.data, null, 2);
    // 支払いリンクにリダイレクト
    window.location.href = response.data.data.url;
  } catch (error) {
    console.error('Failed to create code', error);
    // glovalStore.resultLog = 'createCode で エラーが発生しました';
  }
};
</script>
```

#### 以下のように `test-site/frontend/src/App.vue` を修正して、上記で作成したPayPay支払いボタンを配置
```vuejs
<template>
  <div id="app">
    <h1>PayPay Test Payment</h1>
    <CreateCode />
  </div>
</template>

<script setup>
import CreateCode from './components/CreateCode.vue';
</script>
```

#### `test-site/backend`ディレクトリを作成し、各モジュールをインストール
```bash
mkdir backend && cd backend && pwd
npm init -y
npm install --save @paypayopa/paypayopa-sdk-node express cors dotenv
```

#### Dockerfile作成(`test-site/backend/Dockerfile`)
```dockerfile
FROM node:23
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
```

#### server.js作成(`test-site/backend/server.js`)
```js
const express = require('express');
const cors = require('cors');
const dotenv = require('dotenv');
const app = express();
const PORT = 3000;

app.use(cors());
app.use(express.json());
dotenv.config();

'use strict';
const PAYPAY = require('@paypayopa/paypayopa-sdk-node');
PAYPAY.Configure({
    env: "STAGING",
    clientId: process.env.API_KEY,
    clientSecret: process.env.API_SECRET,
    merchantId: process.env.MERCHANT_ID,
});

app.post('/api/paypay/v2/codes/:amount', async (req, res) => {
  let amount = req.params.amount;
  
  try {
    const requestBody = {
      merchantPaymentId: "now-v2codes-" + Date.now(), // リクエストごとに一意になれば何でもよい
      amount: {
        amount: amount,
        currency: "JPY"
      },
      codeType: "ORDER_QR",
      redirectUrl: "http://localhost:8080/now-v2codes-" + Date.now(),
      redirectType: "WEB_LINK",
      isAuthorization: false
    };
    
    const response = await PAYPAY.QRCodeCreate(requestBody);
    const resbody = response.BODY;

    console.log(response.STATUS, resbody.resultInfo.code);
    res.json(resbody);
  } catch (error) {
    console.error('Create QR Code error:', error);
    res.status(500).json({ error: error.message || 'QR code creation failed' });
  }
});

// app.get('/api/paypay/v2/codes/payments/:merchantPaymentId', async (req, res) => {
//   let merchantPaymentId = req.params.merchantPaymentId;

//   const response = await PAYPAY.GetCodePaymentDetails([merchantPaymentId]);
//   const resbody = response.BODY;
//   console.log(resbody.resultInfo.code);
//   console.log(resbody.data.status);
//   res.json(resbody);
// });

app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});

```

#### .env ファイルを作成(`test-site/backend/.env`)
PayPay for Developers の ダッシュボード に記載されています。
```md
API_KEY = '{{YOUR_APIKEY}}'
API_SECRET = '{{YOUR_SECRET}}'
MERCHANT_ID = '{{YOUR_加盟店ID}}'
```


#### `test-site/docker-compose.yml`にbackendの設定を追記
```yml
services:
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    volumes:
      - ./frontend:/app
      - /app/node_modules
    command: npm run serve
    depends_on:
      - backend

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    volumes:
      - ./backend:/app
    command: node server.js
```

#### これでいったん完成です。
一度、Dockerコンテナ停止・削除 して、再度起動しましょう。

起動したらもう一度8080にアクセスしてみてください。
入力フィールドに決済したい数値を入れて、決済ボタンをクリックすると、PayPayの決済画面にリダイレクトします。

PayPay for Developers の ダッシュボード に3つのテストユーザーがあるので適当なテストユーザーでログインして決済してみましょう。

決済後、もう一度localhost:8080にリダイレクトしてきたら成功です。


## 3. 決済ステータス確認 と ログをフロントエンドに表示
というのを書きたいのですが、記事作成に慣れてなく、労力を割けないのでいったんここで公開させてください...

とはいえ、ここまでくれば Vue も PayPayAPI とかも試せると思うので、いろいろ試してみてくださいー

個人的には以下をやりたいな。今回の応用ですね。
・他の PayPayAPI を 画面上 からリクエストできるようにする
・Vue.js を使ってできることをChatGPTに聞きながら試す
・DockerCompose を使ってできることをChatGPTに聞きながら試す


以上、また気が向いたら更新or投稿します。