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 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