Skip to content

fix: pin the clobbered JSON keys with @JsonKey and surface 5 fields the native SDKs already support - #57

Merged
ZweWaiYanHtet merged 2 commits into
masterfrom
fix-handwritten-json-keys
Aug 6, 2026
Merged

fix: pin the clobbered JSON keys with @JsonKey and surface 5 fields the native SDKs already support#57
ZweWaiYanHtet merged 2 commits into
masterfrom
fix-handwritten-json-keys

Conversation

@masatoi

@masatoi masatoi commented Aug 3, 2026

Copy link
Copy Markdown
Member

Overview

Two things in one PR.

  1. Pin the 4 JSON keys that were clobbered by the clean regeneration in 3d753f8 (bug fix)
  2. Surface 5 fields that the native SDKs 2.0.28 already support but the Dart layer was missing (dropped responses / unsent request params found in an audit)

1. Fixing the JSON keys broken by the clean regeneration

When 3d753f8 (fix: make build_runner output reproducible in CI) regenerated every .g.dart from scratch, the JSON keys of 4 fields changed and were committed to master as-is.

The cause is that these Dart field names do not round-trip through json_serializable's field_rename: snake. That transform inserts a _ before every uppercase character except the first, so names containing acronyms or typos get mangled.

Field Correct JSON key Current state on master
PrivateMoney.onlineMessage oneline_message online_message
PrivateMoney.canUseC2CTransfer can_use_c2c_transfer can_use_c2_c_transfer
BankPayRedirectUrl.redirectUrl redirectUrl redirect_url
BankPayRedirectUrl.paytreeCustomerNumber paytreeCustomerNumber paytree_customer_number

Both PrivateMoney fields are non-nullable (as String / as bool), so PrivateMoney.fromJson throws on every response the API actually returns.

TokenInfo was another casualty of 3d753f8 and was fixed in cd96258, but these 4 were missed.

This PR makes the JSON key explicit with @JsonKey(name: ...) so regeneration on any future version cannot break them again. The field names themselves are left alone since renaming them would be a breaking change to the public API.

  • lib/responses/private_money.dart / .g.dart
  • lib/responses/bankpay_redirect_url.dart / .g.dart

As a side effect, the toJson side of onlineMessage is fixed too. onlineMessage was mistakenly renamed from onelineMessage in 51b7a62; at the time only the fromJson half of the .g.dart was hand-patched back to oneline_message, leaving toJson emitting online_message since 2022.


2. Surfacing fields the native SDKs already support

Comparing pokepay-server's api/views/json/*.lisp against the hand-written SDK turned up 13 response keys the server returns but the SDK never reads, and 14 request parameters the API accepts but the SDK never sends. This PR lands the 5 of those that flutter-sdk can fix on its own.

Why only 5

flutter-sdk does not talk to the API directly. Every call goes through a native SDK.

Dart -> MethodChannel -> pokepaylib 2.0.28 / Pokepay pod 2.0.28 -> server
server JSON -> native typed response class -> re-serialized -> String -> Dart fromJson

On Android, JsonConverter.createObjectMapper() sets FAIL_ON_UNKNOWN_PROPERTIES=false so unknown keys are discarded, and Response.toString() then writes back only the declared fields via writeValueAsString(this). iOS behaves identically with Codable + explicit CodingKeys -> JSONEncoder.

In other words, a key that does not exist on the native class never reaches Dart, and adding the field to the Dart class only ever yields null. The same applies to requests: a parameter absent from the native request class's constructor cannot be sent.

Fixable in flutter-sdk alone Requires android-sdk / ios-sdk changes
Responses 4 9
Requests 1 13

Changes

4 new fields on CvsAuthorization

The payment-slip URL, receipt number, and completion/cancellation timestamps for convenience-store payments. These look necessary in practice.

Key Type
haraikomi_url String?
receipt_no String?
done_at String?
canceled_at String?

Both native SDKs already carry these — Android in BankAPI/autogen/responses/CvsAuthorization.java, iOS in Responses/CvsAuthorization.swift — so adding them to the Dart class is enough to make the values arrive.

done_at / canceled_at are String? rather than DateTime? because both native SDKs type them as String (String? on Swift) and pass the server's RFC3339 string straight through. Account.nearestExpiresAt is DateTime? only because the native layer receives it as a Date and reformats it; the pass-through fields CvsAuthorization.payLimit and UserTransaction.doneAt are already String. This follows that convention.

All 4 are nullable, so fromJson will not break on existing responses.

code on patchAccountCouponDetail

The coupon redemption code. The native side already supports it — Android PatchAccountCouponDetail, iOS PatchCouponDetail — and PokepaySdkPlugin.java already reads call.argument("code") and forwards it. Dart simply never sent it.

The Swift side was missing the code argument on PatchCouponDetail (SwiftPokepaySdkPlugin.swift:509), which this PR fixes as well.

About the remaining 22

CreateBill / UpdateCashtray / UpdateBill / CreateAccountCpmToken / UpdateTerminal / GetBill / GetAccountBalances / CreateCheck have no such parameters on their constructors, and the PrivateMoney / UserTransaction / Account / AccountCpmToken response classes have no such fields.

  • 9 responses: PrivateMoney.display_money_and_point / is_topup_quota_available / is_itrust_authentication_enabled / money_topup_transfer_limit / sounds, UserTransaction.raw_point_amount / campaign_point_amount, Account.status, AccountCpmToken.strategy
  • 13 requests: createAccountCpmToken's keep_alive / is_short_token / strategy, createBill's additional_account_ids / min_amount / max_amount / metadata, createCheck's metadata, products on updateBill / updateCashtray, updateTerminal's push_service, getAccountBalances's expired, getBill's private_money_id

Each of these needs android-sdk and ios-sdk changes -> a 2.0.29 release -> a dependency bump in flutter-sdk, in that order, so they are out of scope here.

For what it's worth, no required parameter is missing anywhere (all 47 operators were checked).


Verification

Static

  • Ran a clean regeneration (build_runner clean + --delete-conflicting-outputs) and confirmed the .g.dart diff is limited to the 3 intended files (the other 179 match master exactly)
  • flutter analyze --no-fatal-warnings -> No issues found!
  • Confirmed can_use_c2c_transfer / redirectUrl / paytreeCustomerNumber match the keys as of 3d753f8^ exactly
  • dart run tool/check_native_links.dart -> 0 errors. The newly added code does not appear in the warnings, which mechanically confirms both Java and Swift read the key Dart sends

Against the live dev environment

Built a test app depending on pokepay_sdk by path and ran it under integration_test on a Pixel_5_API_31 emulator, exercising the real path Dart -> MethodChannel -> pokepaylib 2.0.28 -> api-dev.pokepay.jp. 32 passed / 2 failed / 3 skipped.

The 4 new CvsAuthorization fields carry real values:

haraikomiUrl = https://www.veritrans.co.jp/user_support/econ_dummy.html
receiptNo    = 303003
doneAt       = 2025-07-16T09:18:15.000000Z
canceledAt   = null

This also backs up the String? decision — the server's RFC3339 string arrives verbatim through the native layer.

patchAccountCouponDetail(code) returned 200, and the SDK debug log shows code: smoke-test-code in the MethodChannel argument map. Note that the dev coupon does not require a code, so the response cannot distinguish whether code was sent (verified with curl: identical 200 with and without it). The native-to-server leg is confirmed at the source level only.

The fix in part 1 is confirmed live as well: getPrivateMoney returns canUseC2CTransfer=true. On master this is exactly where the non-nullable fromJson would have thrown looking for can_use_c2_c_transfer.

The 2 failures are server-side data/permission conditions, not SDK bugs: getAccountTopupStats -> 422 private_money_total_topup_limit_not_found, and createCashtray -> 403 Forbidden (this terminal token has no merchant role), which also caused the 3 skips.

The .g.dart files were regenerated with a local Dart 3.0.3 (json_serializable 6.6.x). That differs from the 6.11.2 in pubspec.lock, but since the other 179 generated files matched the committed contents on master exactly, the two versions produce identical output for this codebase. pubspec.lock is unchanged.

Not verified

iOS was not run. The one-line Swift change compiles only in principle — it matches the existing PatchCouponDetail initializer signature, but no Xcode build was performed. Worth a run on a real iOS device before merging.

Note

Two unrelated pre-existing bugs turned up during the dev-environment run and were filed separately: #58 (CPM token detection in getTokenInfo / scanToken does not match the actual token format) and #59 (PokepayClient.topup() passes the string literal 'check.id').

🤖 Generated with Claude Code

masatoi and others added 2 commits August 3, 2026 20:10
3d753f8 regenerated every .g.dart from a clean build. For four fields the
committed JSON keys changed, because their Dart field names do not round-trip
through json_serializable's `field_rename: snake` (which inserts `_` before
every single uppercase letter):

  PrivateMoney.onlineMessage        oneline_message      -> online_message
  PrivateMoney.canUseC2CTransfer    can_use_c2c_transfer -> can_use_c2_c_transfer
  BankPayRedirectUrl.redirectUrl    redirectUrl          -> redirect_url
  BankPayRedirectUrl.paytreeCustomerNumber
                                    paytreeCustomerNumber -> paytree_customer_number

Both are non-nullable in PrivateMoney, so fromJson now throws on every
response the API actually returns. Pin the keys with @jsonkey so they survive
any future regeneration, and regenerate the two affected files.

`onlineMessage` is a typo of `oneline` introduced in 51b7a62; the field name is
left alone to avoid a breaking API change. Note this also repairs its toJson
side, which had been emitting `online_message` since that commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
flutter-sdk はサーバを直接叩かず、ネイティブSDK (pokepaylib / Pokepay pod)
の型付きレスポンスクラスを経由して再シリアライズされた JSON を読む。
Android は FAIL_ON_UNKNOWN_PROPERTIES=false で未知キーを捨てたうえ
Response.toString() が宣言済みフィールドだけを書き戻し、iOS も Codable +
明示 CodingKeys で同じ挙動になる。したがってネイティブ側に無いキーは
Dart まで届かず、Dart にフィールドを足しても常に null になる。

監査で挙がった 27 件のうち、ネイティブ 2.0.28 が既に対応していて
Dart 側だけが欠けている 5 件を通す。

レスポンス (未対応1 のうち 4 件):
  CvsAuthorization に haraikomi_url / receipt_no / done_at / canceled_at を追加。
  Android BankAPI/autogen/responses/CvsAuthorization.java と
  iOS Responses/CvsAuthorization.swift の双方が既に保持している。
  done_at / canceled_at はネイティブが String のまま素通しするため、
  同クラスの pay_limit や UserTransaction.done_at と揃えて String? で受ける。
  4 件とも nullable なので既存レスポンスでも fromJson は落ちない。

リクエスト (未対応2 のうち 1 件):
  patchAccountCouponDetail に code を追加。
  PokepaySdkPlugin.java は既に call.argument("code") を読んで
  PatchAccountCouponDetail に渡しており、Dart が送っていなかった。
  Swift 側は PatchCouponDetail の code 引数を渡し忘れていたので併せて修正。

残り 22 件は CreateBill / UpdateCashtray / CreateAccountCpmToken 等の
コンストラクタ自体にパラメータが無く、PrivateMoney / UserTransaction /
Account / AccountCpmToken のレスポンスクラスにもフィールドが無いため、
android-sdk と ios-sdk の改修とリリースが先に必要になる。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@masatoi masatoi changed the title fix: クリーン再生成で壊れた JSON キーを @JsonKey で固定する fix: 壊れた JSON キーを @JsonKey で固定し、ネイティブ SDK 対応済みの 5 フィールドを Dart に通す Aug 3, 2026
@masatoi
masatoi requested a review from ZweWaiYanHtet August 4, 2026 04:28
@masatoi masatoi changed the title fix: 壊れた JSON キーを @JsonKey で固定し、ネイティブ SDK 対応済みの 5 フィールドを Dart に通す fix: pin the clobbered JSON keys with @JsonKey and surface 5 fields the native SDKs already support Aug 4, 2026

@ZweWaiYanHtet ZweWaiYanHtet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@ZweWaiYanHtet
ZweWaiYanHtet merged commit 9eee92e into master Aug 6, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants