Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

## 前提条件

- Node.js 18 以上
- Node.js 22 以上 (CI 検証バージョン)
- PAY.JP アカウント
- PAY.JP ダッシュボードで Price オブジェクトを作成済み
- PayPay を使うテスト環境
Expand Down Expand Up @@ -48,7 +48,7 @@ PAYJP_WEBHOOK_SECRET=your_webhook_secret

- `PAYJP_SAMPLE_PRODUCT_NAME`: `/products` で返すサンプル商品名。未設定時は `テスト商品`
- `PAYJP_SAMPLE_PRODUCT_AMOUNT`: `/products` で返すサンプル金額。未設定時は `100`
- `ALLOWED_ORIGINS`: CORS 許可オリジン。カンマ区切り。未設定時は全オリジン許可
- `ALLOWED_ORIGINS`: CORS 許可オリジン。カンマ区切り。未設定時は全オリジン許可(**本番環境では必ず設定してください**)
- `PORT`: サーバーポート。既定値は `3000`

### 3. サーバーを起動
Expand Down
8 changes: 4 additions & 4 deletions ios/PayJPCheckoutExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.exmaple.jp.pay.example-v2-checkout.PayJPCheckoutExample";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.jp.pay.example-v2-checkout.PayJPCheckoutExample";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
Expand Down Expand Up @@ -384,7 +384,7 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.exmaple.jp.pay.example-v2-checkout.PayJPCheckoutExample";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.jp.pay.example-v2-checkout.PayJPCheckoutExample";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
Expand All @@ -405,7 +405,7 @@
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.1;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.exmaple.jp.pay.example-v2-checkout.PayJPCheckoutExampleTests";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.jp.pay.example-v2-checkout.PayJPCheckoutExampleTests";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
Expand All @@ -426,7 +426,7 @@
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.1;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.exmaple.jp.pay.example-v2-checkout.PayJPCheckoutExampleTests";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.jp.pay.example-v2-checkout.PayJPCheckoutExampleTests";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
Expand Down
15 changes: 14 additions & 1 deletion server/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require('dotenv').config();
const crypto = require('crypto');
const express = require('express');
const cors = require('cors');

Expand All @@ -7,6 +8,9 @@ const app = express();
const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS
? process.env.ALLOWED_ORIGINS.split(',').map(o => o.trim())
: undefined;
if (!ALLOWED_ORIGINS) {
console.warn('[security] ALLOWED_ORIGINS が未設定のため全オリジンを許可します。本番環境では必ず設定してください。');
}
app.use(cors(ALLOWED_ORIGINS ? { origin: ALLOWED_ORIGINS } : undefined));

app.use('/webhook', express.raw({ type: 'application/json' }));
Expand Down Expand Up @@ -102,6 +106,10 @@ app.post('/create-checkout-session', async (req, res) => {
return res.status(400).json({ error: '必須パラメータが不足しています' });
}

if (!Number.isInteger(quantity) || quantity < 1 || quantity > 100) {
return res.status(400).json({ error: 'quantity は 1〜100 の整数で指定してください' });
}

if (!isAllowedRedirectUrl(success_url) || !isAllowedRedirectUrl(cancel_url)) {
return res.status(400).json({ error: '許可されていないURLスキームです' });
}
Expand Down Expand Up @@ -157,7 +165,12 @@ app.post('/webhook', (req, res) => {
return res.status(500).json({ error: 'Webhook secret is not configured' });
}

if (webhookToken !== expectedToken) {
const tokenBuf = Buffer.from(String(webhookToken ?? ''), 'utf8');
const expectedBuf = Buffer.from(expectedToken, 'utf8');
if (
tokenBuf.length !== expectedBuf.length ||
!crypto.timingSafeEqual(tokenBuf, expectedBuf)
) {
console.error('Invalid webhook token');
return res.status(401).json({ error: 'Invalid webhook token' });
}
Expand Down
Loading