nodexpay 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +125 -0
- data/docs/README.md +49 -0
- data/docs/api-contract.md +203 -0
- data/docs/architecture.md +226 -0
- data/docs/http-and-errors.md +157 -0
- data/docs/implementation-plan.md +130 -0
- data/docs/payment-results.md +109 -0
- data/docs/testing.md +175 -0
- data/lib/nodex_pay/amount_serializer.rb +78 -0
- data/lib/nodex_pay/client.rb +240 -0
- data/lib/nodex_pay/errors.rb +56 -0
- data/lib/nodex_pay/models.rb +40 -0
- data/lib/nodex_pay/signer.rb +22 -0
- data/lib/nodex_pay/transport.rb +94 -0
- data/lib/nodex_pay/validator.rb +106 -0
- data/lib/nodex_pay/version.rb +5 -0
- data/lib/nodex_pay.rb +11 -0
- data/rbi/nodex_pay.rbi +243 -0
- data/sig/nodex_pay.rbs +165 -0
- metadata +162 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0fb30ad3f3fb5a354c9999d7fb6646ddc2e774bc69a5e015d63ffbe77b45e3a3
|
|
4
|
+
data.tar.gz: d0c36c2d71fd2380125befefc0750cc2109b5994c443e325992233743cb09956
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 236d4779b29f5552ce0c091c8f69e8944b6ca7bca4d0664e581d41954171f3a17785a503f9860d8bf43e2253f2d8c86bc4ce64ba1874a73da595a32e0a19d682
|
|
7
|
+
data.tar.gz: a62cb00c75beb8b19367e0afc077f4f79b9e6a4fcdb7baaf60e20d9bc7af83163b130d134bd24c14e8af303f24ceb891f72d761024f02076a8379a130b0e90ff
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 supermomonga
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
data/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# nodexpay
|
|
2
|
+
|
|
3
|
+
Nodex Pay API の非公式 Ruby クライアント SDK です。支払いの作成・取消と、Payment Result callback の署名検証を提供します。Ruby 3.4 以上に対応し、runtime dependency はありません。
|
|
4
|
+
|
|
5
|
+
> [!IMPORTANT]
|
|
6
|
+
> この gem は Nodex Pay または Nodex Global Limited が提供・承認する公式 SDK ではありません。実運用前に [Nodex Pay API ドキュメント](https://nodex-pay.gitbook.io/docs/integration-guide/apis) と Testnet で挙動を確認してください。
|
|
7
|
+
|
|
8
|
+
## インストール
|
|
9
|
+
|
|
10
|
+
Gemfile に追加します。
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
gem "nodexpay"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
または直接インストールします。
|
|
17
|
+
|
|
18
|
+
```shell
|
|
19
|
+
gem install nodexpay
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## クライアント
|
|
23
|
+
|
|
24
|
+
Production と Testnet の取り違えを防ぐため、`environment` に既定値はありません。
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
require "nodex_pay"
|
|
28
|
+
|
|
29
|
+
client = NodexPay::Client.new(
|
|
30
|
+
identification_token: ENV.fetch("NODEX_PAY_IDENTIFICATION_TOKEN"),
|
|
31
|
+
hash_token: ENV.fetch("NODEX_PAY_HASH_TOKEN"),
|
|
32
|
+
environment: :testnet
|
|
33
|
+
)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
必要に応じて `open_timeout`、`read_timeout`、`write_timeout` を秒単位で指定できます。既定値はそれぞれ5秒、30秒、30秒です。
|
|
37
|
+
|
|
38
|
+
## 支払いを作成する
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
payment = client.create_payment(
|
|
42
|
+
order_code: "order-123",
|
|
43
|
+
amount: "100.00",
|
|
44
|
+
amount_type: "JPY",
|
|
45
|
+
color_theme: :light,
|
|
46
|
+
deadline: Time.now + 3600,
|
|
47
|
+
description: "Order #123"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
payment.url
|
|
51
|
+
payment.token
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`amount` には `String`、`Integer`、または利用アプリケーションが `bigdecimal` を導入済みの場合は `BigDecimal` を指定できます。署名精度を曖昧にしないため `Float` は受け付けません。`amount` を省略すると、支払者が金額を決める支払いを作成します。
|
|
55
|
+
|
|
56
|
+
`order_code` は Payment Request ごとに一意な ASCII 文字列を指定し、アプリケーション側で保存してください。
|
|
57
|
+
|
|
58
|
+
## 支払いを取り消す
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
client.cancel_payment(order_code: "order-123") #=> true
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
未払いかつ blockchain transaction が関連付けられていない支払いだけを取り消せます。すでに取消済みの場合も例外を返します。
|
|
65
|
+
|
|
66
|
+
## Payment Result を検証する
|
|
67
|
+
|
|
68
|
+
Web framework が受信した raw JSON body をそのまま渡します。
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
result = client.verify_payment_result(raw_request_body)
|
|
72
|
+
|
|
73
|
+
if result.success?
|
|
74
|
+
# order_code、amount、symbol を保存済み注文と照合して状態を更新する
|
|
75
|
+
end
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
署名不一致時は `NodexPay::SignatureVerificationError`、JSON や field が不正な場合は `NodexPay::InvalidResponseError` を送出します。
|
|
79
|
+
|
|
80
|
+
> [!WARNING]
|
|
81
|
+
> Nodex Pay の署名対象は `order_code` と `amount` だけです。`result`、`symbol`、chain/transaction field の完全性や blockchain finality は署名だけでは保証されません。注文との照合、`transaction_code` の重複排除、必要に応じた on-chain 確認をアプリケーション側で行ってください。
|
|
82
|
+
|
|
83
|
+
## エラー処理
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
begin
|
|
87
|
+
client.create_payment(order_code: "order-123", amount: "100")
|
|
88
|
+
rescue NodexPay::BadRequestError => error
|
|
89
|
+
warn "status=#{error.status} codes=#{error.error_codes.inspect}"
|
|
90
|
+
rescue NodexPay::TimeoutError, NodexPay::TransportError => error
|
|
91
|
+
# 自動 retry はせず、結果不明として業務上の確認を行う
|
|
92
|
+
end
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
POST の自動 retry と redirect 追従は行いません。API error の詳細は `status`、`error_codes`、`response_headers`、`response_body` から確認できます。response body は機密情報として扱い、そのままログへ出力しないでください。
|
|
96
|
+
|
|
97
|
+
## 型定義
|
|
98
|
+
|
|
99
|
+
RBS は `sig/nodex_pay.rbs`、Sorbet RBI は `rbi/nodex_pay.rbi` として gem に同梱しています。SDK 本体は `sorbet-runtime` に依存しません。
|
|
100
|
+
|
|
101
|
+
標準の `srb tc` wrapper は、Bundler の依存に含まれる gem の `rbi/` を検出するため、追加の require やコピーは不要です。Tapioca で gem RBI を管理する project では、その project の Tapioca 更新フローに従ってください。
|
|
102
|
+
|
|
103
|
+
RBI は `NodexPay::Client`、返却モデル、transport value、公開例外を型付けします。custom transport は duck typing のため `T.untyped` とし、`call(Request) -> Response` の契約は設計文書で定義しています。
|
|
104
|
+
|
|
105
|
+
開発時の `bundle exec rake sorbet` はRBIを `typed: strict` で検査し、Sorbet metrics上のsignature coverage(`sig` 数 ÷ 検出method数)が100%でなければ失敗します。CIでも同じtaskを実行するため、型エラーや型定義の追加漏れを許容しません。
|
|
106
|
+
|
|
107
|
+
## 開発
|
|
108
|
+
|
|
109
|
+
```shell
|
|
110
|
+
bundle install
|
|
111
|
+
bundle exec rake
|
|
112
|
+
bundle exec rake build
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Testnet の資格情報を設定した場合に限り、支払い作成と取消の手動 smoke test を実行できます。
|
|
116
|
+
|
|
117
|
+
```shell
|
|
118
|
+
NODEX_PAY_IDENTIFICATION_TOKEN=... \
|
|
119
|
+
NODEX_PAY_HASH_TOKEN=... \
|
|
120
|
+
bundle exec rake smoke:testnet
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
この task は実際に Testnet の支払いを1件作成し、直後に取り消します。通信結果不明時の自動 retry は行いません。
|
|
124
|
+
|
|
125
|
+
設計判断、wire contract、テスト戦略は [docs/README.md](docs/README.md) から参照できます。
|
data/docs/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Nodex Pay Ruby SDK 設計文書
|
|
2
|
+
|
|
3
|
+
このディレクトリは、Nodex Pay API の非公式 Ruby クライアント SDK `nodexpay` の設計仕様を管理します。実装者、レビュー担当者、SDK を組み込むアプリケーション開発者を対象としています。
|
|
4
|
+
|
|
5
|
+
> [!IMPORTANT]
|
|
6
|
+
> この SDK は Nodex Pay または Nodex Global Limited が提供・承認する公式 SDK ではありません。API の正確な挙動については、必ず Nodex Pay の公式ドキュメントと利用環境で確認してください。
|
|
7
|
+
|
|
8
|
+
## 文書一覧
|
|
9
|
+
|
|
10
|
+
| 文書 | 内容 |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| [architecture.md](architecture.md) | 公開 API、内部構造、モデル、transport、RBS/Sorbet RBI |
|
|
13
|
+
| [api-contract.md](api-contract.md) | Payment Request / Cancel の wire contract と署名 |
|
|
14
|
+
| [payment-results.md](payment-results.md) | Payment Result の解析、署名検証、セキュリティ境界 |
|
|
15
|
+
| [http-and-errors.md](http-and-errors.md) | HTTP 動作、timeout、例外、エラーレスポンス |
|
|
16
|
+
| [testing.md](testing.md) | テスト戦略、固定ベクトル、CI、testnet smoke test |
|
|
17
|
+
| [implementation-plan.md](implementation-plan.md) | 実装順序、リリース条件、完了基準 |
|
|
18
|
+
|
|
19
|
+
## 対象範囲
|
|
20
|
+
|
|
21
|
+
初版では次の3つを扱います。
|
|
22
|
+
|
|
23
|
+
- Nodex Pay へ支払いを作成する Payment Request API
|
|
24
|
+
- 未払いの支払いを取り消す Payment Cancel API
|
|
25
|
+
- 加盟店サーバーが受信する Payment Result の解析と署名検証
|
|
26
|
+
|
|
27
|
+
Rails 固有の controller や routing、加盟店のデータベース、注文状態管理、加盟店が独自実装する Payment Status API は対象外です。
|
|
28
|
+
|
|
29
|
+
## 用語
|
|
30
|
+
|
|
31
|
+
| 用語 | 意味 |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| Business Client / 加盟店 | Nodex Pay を自サービスへ組み込む事業者 |
|
|
34
|
+
| payer / 支払者 | Nodex Pay の支払い画面で支払いを実行する利用者 |
|
|
35
|
+
| Identification Token | API body の `identification_token` として送る認証情報 |
|
|
36
|
+
| Hash Token | SHA-256 値の生成に使う秘密情報。Nodex Pay へは送信しない |
|
|
37
|
+
| Payment Result | 支払い結果確定時に Nodex Pay から加盟店へ送られる JSON POST |
|
|
38
|
+
| wire value | HTTP body へ実際に書き込む文字列表現 |
|
|
39
|
+
|
|
40
|
+
## 仕様の根拠と優先順位
|
|
41
|
+
|
|
42
|
+
仕様は 2026-07-18 に確認しました。記載が食い違う場合は、次の順で判断します。
|
|
43
|
+
|
|
44
|
+
1. 現行の [Payment Request API](https://nodex-pay.gitbook.io/docs/integration-guide/apis/payment-request-api.md)、[Payment Result API](https://nodex-pay.gitbook.io/docs/integration-guide/apis/payment-result-api.md)、[Payment Cancel API](https://nodex-pay.gitbook.io/docs/integration-guide/apis/payment-cancel-api.md)
|
|
45
|
+
2. 現行の [Integration Guide](https://nodex-pay.gitbook.io/docs/integration-guide/integration-guide/implement-apis.md) と [Developer FAQ](https://nodex-pay.gitbook.io/docs/support/faq/developers-business-client-integrator.md)
|
|
46
|
+
3. 公式ページに掲載された Ruby サンプル
|
|
47
|
+
4. 前身サービスの公開サンプルは挙動調査の参考に限り、現行契約の根拠にはしない
|
|
48
|
+
|
|
49
|
+
公式文書だけでは確定できない項目は、各文書で「公式記載」「SDK の採用判断」「Nodex 側への確認事項」に分けています。推測によるパラメーター追加や旧仕様へのフォールバックは行いません。
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Outbound API 契約
|
|
2
|
+
|
|
3
|
+
この文書は、Payment Request API と Payment Cancel API に対する SDK の wire contract を定義します。
|
|
4
|
+
|
|
5
|
+
## 環境と endpoint
|
|
6
|
+
|
|
7
|
+
| 環境 | Base URL | Payment Request | Payment Cancel |
|
|
8
|
+
| --- | --- | --- | --- |
|
|
9
|
+
| Production | `https://app.nodexpay.com/api/v1` | `POST /payment/receive` | `POST /payment/cancel` |
|
|
10
|
+
| Testnet | `https://testnet.app.nodexpay.com/api/v1` | `POST /payment/receive` | `POST /payment/cancel` |
|
|
11
|
+
|
|
12
|
+
`environment:` は `:production` または `:testnet` を必須とします。独自 base URL、別 domain への自動切り替え、到達不能時のフォールバックは提供しません。
|
|
13
|
+
|
|
14
|
+
公式 Java サンプルにある `testnet.nodexpay.app.com` は、他のサンプルと一致せず証明書の host name とも一致しないため採用しません。旧 `slash.fi` domain も採用しません。
|
|
15
|
+
|
|
16
|
+
## 共通 HTTP 形式
|
|
17
|
+
|
|
18
|
+
SDK は現行公式 Ruby サンプルの `Net::HTTP.post_form` に合わせ、両 endpoint を `application/x-www-form-urlencoded` で送信します。
|
|
19
|
+
|
|
20
|
+
```http
|
|
21
|
+
Accept: application/json
|
|
22
|
+
Content-Type: application/x-www-form-urlencoded
|
|
23
|
+
User-Agent: nodexpay/<gem-version> ruby/<ruby-version>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Node.js の Integration Guide には JSON body の例もありますが、API ページは Content-Type を規定していません。Ruby SDK として wire format を一意にし、署名に用いた文字列がそのまま form field になることを優先します。
|
|
27
|
+
|
|
28
|
+
## 認証と署名
|
|
29
|
+
|
|
30
|
+
- `identification_token` は各 request body に含めます。
|
|
31
|
+
- `hash_token` は SHA-256 の入力にのみ使い、request body や header へ含めません。
|
|
32
|
+
- SHA-256 の出力は小文字16進文字列です。HMAC ではありません。
|
|
33
|
+
- 区切り文字は ASCII の `::` です。
|
|
34
|
+
|
|
35
|
+
金額がある Payment Request の署名対象:
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
<order_code>::<normalized_amount>::<hash_token>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
金額がない Payment Request と Payment Cancel の署名対象:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
<order_code>::::<hash_token>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 共通 validation
|
|
48
|
+
|
|
49
|
+
### `order_code`
|
|
50
|
+
|
|
51
|
+
- String のみ
|
|
52
|
+
- 空文字列は不可
|
|
53
|
+
- ASCII の single-byte character のみ
|
|
54
|
+
- 255 bytes 以下
|
|
55
|
+
- 前後の空白を SDK が暗黙に除去しない
|
|
56
|
+
|
|
57
|
+
Payment Request ごとに一意な値を利用者が生成・保存する必要があります。SDK は一意性を検査できません。同じ `order_code` の再送は error code 2021 になり得ますが、元の成功レスポンスを再取得できる冪等 API ではありません。
|
|
58
|
+
|
|
59
|
+
### 金額の正規化
|
|
60
|
+
|
|
61
|
+
`amount` は `String`、`Integer`、`BigDecimal` を受け付けます。`Float`、指数表記、NaN、Infinity、符号付きゼロは拒否します。
|
|
62
|
+
|
|
63
|
+
正規化手順は次のとおりです。
|
|
64
|
+
|
|
65
|
+
1. String は `\A(?:0|[1-9][0-9]*)(?:\.[0-9]+)?\z` に一致することを確認する。
|
|
66
|
+
2. `BigDecimal` で値を検証する。
|
|
67
|
+
3. 指数を使わない10進表記へ変換する。
|
|
68
|
+
4. 小数部末尾の `0` を除去し、小数部がなくなった場合は小数点も除去する。
|
|
69
|
+
5. 正規化後の同じ String を署名入力と form field に使う。
|
|
70
|
+
|
|
71
|
+
例:
|
|
72
|
+
|
|
73
|
+
| 入力 | wire value |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| `10` | `"10"` |
|
|
76
|
+
| `"10.00"` | `"10"` |
|
|
77
|
+
| `BigDecimal("10.50")` | `"10.5"` |
|
|
78
|
+
| `"0.000001"` | `"0.000001"` |
|
|
79
|
+
|
|
80
|
+
金額指定時の範囲は `0.000001..100000000`(両端を含む)です。公式説明にある “Zero or more” より、同じ field の詳細な範囲記載を優先します。
|
|
81
|
+
|
|
82
|
+
## Payment Request
|
|
83
|
+
|
|
84
|
+
### 公開メソッド
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
client.create_payment(
|
|
88
|
+
order_code:,
|
|
89
|
+
amount: nil,
|
|
90
|
+
amount_type: nil,
|
|
91
|
+
color_theme: nil,
|
|
92
|
+
ui_mode: nil,
|
|
93
|
+
ext_description: nil,
|
|
94
|
+
deadline: nil,
|
|
95
|
+
description: nil
|
|
96
|
+
) #=> NodexPay::Payment
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### request field
|
|
100
|
+
|
|
101
|
+
| Ruby keyword | Wire field | 必須 | 契約 |
|
|
102
|
+
| --- | --- | --- | --- |
|
|
103
|
+
| 内部設定 | `identification_token` | 必須 | 空でない String |
|
|
104
|
+
| `order_code` | `order_code` | 必須 | 共通 validation に従う |
|
|
105
|
+
| SDK 生成 | `verify_token` | 必須 | SHA-256 小文字 hex |
|
|
106
|
+
| `amount` | `amount` | 任意 | 正規化済みの10進 String。nil 時は key 自体を省略 |
|
|
107
|
+
| `color_theme` | `color_theme` | 任意 | `dark` または `light`。Symbol/String を小文字へ正規化 |
|
|
108
|
+
| `amount_type` | `amount_type` | 任意 | 下記通貨。Symbol/String を大文字へ正規化 |
|
|
109
|
+
| `ui_mode` | `uimode` | 任意 | `switchable` のみ |
|
|
110
|
+
| `ext_description` | `ext_description` | 任意 | 有効な UTF-8、100 characters 以下 |
|
|
111
|
+
| `deadline` | `deadline` | 任意 | `Time` または非負 Integer。Unix time 秒へ変換 |
|
|
112
|
+
| `description` | `description` | 任意 | 有効な UTF-8、100 characters 以下 |
|
|
113
|
+
|
|
114
|
+
`amount_type` の許可値:
|
|
115
|
+
|
|
116
|
+
```text
|
|
117
|
+
USD JPY EUR AED SGD HKD CAD IDR PHP INR KRW
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
SDK は `amount_type` が加盟店管理画面の設定と一致するかを確認できません。`deadline` が将来時刻かどうかも公式の client-side validation として規定されていないため、非負整数への変換だけを行います。
|
|
121
|
+
|
|
122
|
+
### 成功レスポンス
|
|
123
|
+
|
|
124
|
+
HTTP 200 で、次の JSON object を期待します。
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"url": "https://app.nodexpay.com/payment/PAYMENT_TOKEN",
|
|
129
|
+
"token": "PAYMENT_TOKEN",
|
|
130
|
+
"ext_reserved": "",
|
|
131
|
+
"ext_description": ""
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
- `url` と `token` は必須の String として検査します。
|
|
136
|
+
- `ext_reserved` と `ext_description` は欠落または null を許容し、String または nil として保持します。
|
|
137
|
+
- 必須 field の欠落、型違い、JSON 以外の body は `InvalidResponseError` にします。
|
|
138
|
+
- redirect URL へ SDK 自身がアクセスすることはありません。
|
|
139
|
+
|
|
140
|
+
### `ext_reserved` と `callback_url`
|
|
141
|
+
|
|
142
|
+
現行ページでは `ext_reserved` がレスポンス例、説明、error code 2101 に現れますが request table にありません。SDK はレスポンスを保持するだけで、`create_payment` の入力には追加しません。
|
|
143
|
+
|
|
144
|
+
`callback_url` も error code 2040 にだけ現れ、現行 request table にはありません。旧仕様との互換性のために追加しません。
|
|
145
|
+
|
|
146
|
+
## Payment Cancel
|
|
147
|
+
|
|
148
|
+
### 公開メソッド
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
client.cancel_payment(order_code:) #=> true
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### request field
|
|
155
|
+
|
|
156
|
+
| Wire field | 値 |
|
|
157
|
+
| --- | --- |
|
|
158
|
+
| `identification_token` | client の設定値 |
|
|
159
|
+
| `order_code` | validation 済みの引数 |
|
|
160
|
+
| `verify_token` | `SHA256("#{order_code}::::#{hash_token}")` |
|
|
161
|
+
|
|
162
|
+
現行 Cancel schema には `amount` がありません。そのため署名は amount なしの形式へ固定し、公開メソッドにも amount 引数を設けません。
|
|
163
|
+
|
|
164
|
+
### 成功レスポンス
|
|
165
|
+
|
|
166
|
+
公式レスポンス例は具体的な schema を示していません。SDK は HTTP 200 だけを成功条件とし、body を解釈せず `true` を返します。
|
|
167
|
+
|
|
168
|
+
HTTP 400 の代表的 error code:
|
|
169
|
+
|
|
170
|
+
| Code | Name |
|
|
171
|
+
| --- | --- |
|
|
172
|
+
| 2010 | `RECEIVED_IDENTIFICATION_TOKEN_EMPTY` |
|
|
173
|
+
| 2020 | `RECEIVED_ORDER_CODE_EMPTY` |
|
|
174
|
+
| 2022 | `RECEIVED_ORDER_CODE_ALREADY_CANCELLED` |
|
|
175
|
+
| 2023 | `PAYMENT_NOT_FOUND` |
|
|
176
|
+
| 2024 | `PAYMENT_NOT_CANCELABLE` |
|
|
177
|
+
| 2060 | `RECEIVED_VERIFY_TOKEN_EMPTY` |
|
|
178
|
+
| 2061 | `RECEIVED_VERIFY_TOKEN_INVALID` |
|
|
179
|
+
|
|
180
|
+
2022 は例外のまま利用者へ返します。SDK が状態を推測して成功へ変換することはありません。
|
|
181
|
+
|
|
182
|
+
## 公式記載と採用判断
|
|
183
|
+
|
|
184
|
+
| 論点 | 公式記載 | SDK の採用判断 |
|
|
185
|
+
| --- | --- | --- |
|
|
186
|
+
| outbound Content-Type | 未規定。form と JSON の例が混在 | 現行 Ruby サンプルに合わせ form-urlencoded |
|
|
187
|
+
| amount 最小値 | “Zero or more” と `0.000001` が混在 | 詳細 field 定義の `0.000001` を採用 |
|
|
188
|
+
| amount の文字列表現 | 言語別サンプルで `10`、`10.00`、`10.000000` | 送信前に一意に正規化し、署名と body で共有 |
|
|
189
|
+
| Cancel の署名 | amount 有無の一般説明だが body に amount なし | amount なしの4連コロンを採用 |
|
|
190
|
+
| `ext_reserved` | 入力説明があるが request table に field なし | 入力対象外、response のみ保持 |
|
|
191
|
+
| 支払いの無効化 | 古い edge-case 文書は不可と記載 | 更新日の新しい Cancel API を優先 |
|
|
192
|
+
|
|
193
|
+
## Nodex 側への確認事項
|
|
194
|
+
|
|
195
|
+
1. Payment Request / Cancel が正式に保証する Content-Type
|
|
196
|
+
2. server 側の amount 文字列処理と正規化規則
|
|
197
|
+
3. Cancel の署名が常に amount なしであること
|
|
198
|
+
4. Cancel 200 の実際の response body
|
|
199
|
+
5. `ext_reserved` が現行 API の入力として利用可能か
|
|
200
|
+
6. Testnet の Cancel endpoint と Production との差異
|
|
201
|
+
|
|
202
|
+
これらは testnet contract test で確認します。確認前に推測した fallback や追加 keyword は実装しません。
|
|
203
|
+
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# アーキテクチャ
|
|
2
|
+
|
|
3
|
+
## 設計目標
|
|
4
|
+
|
|
5
|
+
`nodexpay` は、Nodex Pay の3つの API 境界を小さく明示的な Ruby API として提供します。
|
|
6
|
+
|
|
7
|
+
- 認証情報と環境をクライアントインスタンスへ閉じ込める
|
|
8
|
+
- 金額の署名値と送信値を必ず同じ serializer から生成する
|
|
9
|
+
- HTTP や JSON の詳細をモデルと例外へ変換する
|
|
10
|
+
- Payment Result をフレームワーク非依存で検証する
|
|
11
|
+
- Nodex Pay が規定していない再試行、状態管理、後方互換処理を追加しない
|
|
12
|
+
|
|
13
|
+
## パッケージ識別子
|
|
14
|
+
|
|
15
|
+
| 項目 | 値 |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| RubyGems 名 | `nodexpay` |
|
|
18
|
+
| require path | `nodex_pay` |
|
|
19
|
+
| Ruby 名前空間 | `NodexPay` |
|
|
20
|
+
| 最低 Ruby | 3.4 |
|
|
21
|
+
| ライセンス | MIT |
|
|
22
|
+
| 位置づけ | 非公式 SDK |
|
|
23
|
+
|
|
24
|
+
## 公開 API
|
|
25
|
+
|
|
26
|
+
### クライアント生成
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
require "nodex_pay"
|
|
30
|
+
|
|
31
|
+
client = NodexPay::Client.new(
|
|
32
|
+
identification_token: ENV.fetch("NODEX_PAY_IDENTIFICATION_TOKEN"),
|
|
33
|
+
hash_token: ENV.fetch("NODEX_PAY_HASH_TOKEN"),
|
|
34
|
+
environment: :testnet,
|
|
35
|
+
open_timeout: 5,
|
|
36
|
+
read_timeout: 30,
|
|
37
|
+
write_timeout: 30,
|
|
38
|
+
transport: nil
|
|
39
|
+
)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`identification_token`、`hash_token`、`environment` は必須です。環境の既定値は設けません。timeout の単位は秒で、正の数を受け付けます。`transport: nil` の場合は既定の `Net::HTTP` transport を使用します。
|
|
43
|
+
|
|
44
|
+
クライアントは生成後に設定を変更できない immutable object とします。グローバルな `NodexPay.configure` は提供しません。
|
|
45
|
+
|
|
46
|
+
### 支払い作成
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
payment = client.create_payment(
|
|
50
|
+
order_code: "order-123",
|
|
51
|
+
amount: "100.00",
|
|
52
|
+
amount_type: "JPY",
|
|
53
|
+
color_theme: :light,
|
|
54
|
+
ui_mode: :switchable,
|
|
55
|
+
ext_description: "External extension data",
|
|
56
|
+
deadline: Time.now + 3600,
|
|
57
|
+
description: "Order #123"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
payment.url #=> "https://..."
|
|
61
|
+
payment.token #=> "..."
|
|
62
|
+
payment.ext_reserved #=> String or nil
|
|
63
|
+
payment.ext_description #=> String or nil
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`amount: nil` または省略時は、支払者が支払額を決める支払いを作成します。その他の入力契約は [api-contract.md](api-contract.md) で定義します。
|
|
67
|
+
|
|
68
|
+
### 支払い取消
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
client.cancel_payment(order_code: "order-123") #=> true
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
HTTP 200 のみを成功とし、それ以外は例外を送出します。すでに取消済みであることを示す error code 2022 も成功へ読み替えません。
|
|
75
|
+
|
|
76
|
+
### Payment Result 検証
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
result = client.verify_payment_result(raw_request_body)
|
|
80
|
+
|
|
81
|
+
result.order_code
|
|
82
|
+
result.transaction_code
|
|
83
|
+
result.amount
|
|
84
|
+
result.symbol
|
|
85
|
+
result.result
|
|
86
|
+
result.success?
|
|
87
|
+
result.chain_id
|
|
88
|
+
result.transaction_hash
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
引数は HTTP request の raw JSON body でなければなりません。JSON の解析、必須 field の型検査、署名検証に成功した場合だけ `PaymentResult` を返します。詳細は [payment-results.md](payment-results.md) で定義します。
|
|
92
|
+
|
|
93
|
+
## 公開モデル
|
|
94
|
+
|
|
95
|
+
モデルには Ruby の `Data` を使います。すべて immutable で、元の可変 Hash は保持しません。
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
NodexPay::Payment = Data.define(
|
|
99
|
+
:url,
|
|
100
|
+
:token,
|
|
101
|
+
:ext_reserved,
|
|
102
|
+
:ext_description
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
NodexPay::PaymentResult = Data.define(
|
|
106
|
+
:order_code,
|
|
107
|
+
:transaction_code,
|
|
108
|
+
:amount,
|
|
109
|
+
:symbol,
|
|
110
|
+
:result,
|
|
111
|
+
:chain_id,
|
|
112
|
+
:transaction_hash
|
|
113
|
+
) do
|
|
114
|
+
def success? = result
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`PaymentResult#amount` は `BigDecimal` へ変換せず、Nodex Pay が送信した文字列を保持します。これは署名検証に元の文字列表現が必要なためです。
|
|
119
|
+
|
|
120
|
+
## データフロー
|
|
121
|
+
|
|
122
|
+
### Outbound API
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
Client
|
|
126
|
+
-> endpoint-specific validator
|
|
127
|
+
-> amount serializer
|
|
128
|
+
-> SHA-256 signer
|
|
129
|
+
-> form body encoder
|
|
130
|
+
-> Transport#call
|
|
131
|
+
-> status/error decoder
|
|
132
|
+
-> immutable model or exception
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
validation、金額変換、署名、response decoding は endpoint ごとに責務を分けます。汎用 Hash を組み立てて各層で再変換する設計は採用しません。
|
|
136
|
+
|
|
137
|
+
### Payment Result
|
|
138
|
+
|
|
139
|
+
```text
|
|
140
|
+
raw JSON body
|
|
141
|
+
-> strict JSON parser
|
|
142
|
+
-> field/type validator
|
|
143
|
+
-> SHA-256 calculation
|
|
144
|
+
-> constant-time comparison
|
|
145
|
+
-> immutable PaymentResult
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
署名不一致時に未検証のモデルを返す API や `valid?: false` のような継続可能な結果は提供しません。
|
|
149
|
+
|
|
150
|
+
## Transport インターフェース
|
|
151
|
+
|
|
152
|
+
HTTP 実装の差し替え境界は次の2モデルと `call` メソッドだけです。
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
NodexPay::Transport::Request = Data.define(
|
|
156
|
+
:method,
|
|
157
|
+
:uri,
|
|
158
|
+
:headers,
|
|
159
|
+
:body,
|
|
160
|
+
:open_timeout,
|
|
161
|
+
:read_timeout,
|
|
162
|
+
:write_timeout
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
NodexPay::Transport::Response = Data.define(
|
|
166
|
+
:status,
|
|
167
|
+
:headers,
|
|
168
|
+
:body
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
class CustomTransport
|
|
172
|
+
def call(request)
|
|
173
|
+
# NodexPay::Transport::Response を返す
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
要件は次のとおりです。
|
|
179
|
+
|
|
180
|
+
- `status` は Integer、`headers` は小文字 key の Hash、`body` は String を返す
|
|
181
|
+
- transport は request body を再シリアライズしない
|
|
182
|
+
- redirect、retry、response decoding を transport の責務に含めない
|
|
183
|
+
- timeout と接続エラーは [http-and-errors.md](http-and-errors.md) の例外へ変換する
|
|
184
|
+
|
|
185
|
+
`Request` と `Response` は transport 実装者向けの公開 extension interface とし、RBS に含めます。
|
|
186
|
+
|
|
187
|
+
## スレッド安全性
|
|
188
|
+
|
|
189
|
+
`Client`、設定値、モデル、request/response は生成後に変更しません。既定 transport はリクエスト間で可変な `Net::HTTP` connection を共有せず、呼び出しごとに接続を生成します。そのため1つの `Client` を複数スレッドから呼び出せます。
|
|
190
|
+
|
|
191
|
+
利用者が注入する custom transport のスレッド安全性は、その transport 実装者の責任です。
|
|
192
|
+
|
|
193
|
+
## 型定義
|
|
194
|
+
|
|
195
|
+
### RBS
|
|
196
|
+
|
|
197
|
+
`sig/nodex_pay.rbs` に次を収録します。
|
|
198
|
+
|
|
199
|
+
- `Client` の initializer と3つの公開メソッド
|
|
200
|
+
- 各 keyword 引数の union type
|
|
201
|
+
- `Payment`、`PaymentResult`
|
|
202
|
+
- transport の `Request`、`Response` と interface
|
|
203
|
+
- すべての公開例外と属性
|
|
204
|
+
|
|
205
|
+
### Sorbet RBI
|
|
206
|
+
|
|
207
|
+
`rbi/nodex_pay.rbi` に RBS と同じ公開契約を収録し、gem package に同梱します。
|
|
208
|
+
|
|
209
|
+
- `Client` の initializer と3つの公開メソッド
|
|
210
|
+
- `Payment`、`PaymentResult`
|
|
211
|
+
- transport の `Request`、`Response`、`NetHTTP#call`
|
|
212
|
+
- すべての公開例外と `APIError` の属性
|
|
213
|
+
|
|
214
|
+
Sorbet は structural interface を表現しないため、runtime に marker module を追加せず、custom transport 引数だけを `T.untyped` とします。transport の具体的な request/response と既定 `NetHTTP` は型付けします。
|
|
215
|
+
|
|
216
|
+
標準の `srb tc` wrapper は Bundler の依存に含まれる gem の `rbi/` を自動検出します。Tapioca を使う project では、その project の gem RBI 更新フローに従います。SDK 本体は `sorbet-runtime` に依存せず、Sorbet は開発時の RBI 検証にだけ使用します。
|
|
217
|
+
|
|
218
|
+
## 対象外
|
|
219
|
+
|
|
220
|
+
- Rails/Rack の controller、middleware、routing
|
|
221
|
+
- Webhook endpoint の HTTP response 生成
|
|
222
|
+
- 注文・支払い状態の永続化
|
|
223
|
+
- Webhook の重複排除や処理順制御
|
|
224
|
+
- blockchain explorer / JSON-RPC による着金確認
|
|
225
|
+
- 独自 base URL、proxy 設定、Faraday adapter
|
|
226
|
+
- Nodex Pay の非公開または旧 API
|