Skip to content

MS-496: Prepayment hook callback, flutter#18

Merged
vscmaster merged 9 commits intomainfrom
feat/MS-496-Prepayment-hook-callback-flutter
Feb 10, 2026
Merged

MS-496: Prepayment hook callback, flutter#18
vscmaster merged 9 commits intomainfrom
feat/MS-496-Prepayment-hook-callback-flutter

Conversation

@vscmaster
Copy link
Contributor

@vscmaster vscmaster commented Feb 5, 2026

@vscmaster
Copy link
Contributor Author

@jab3z , ready for review

@vscmaster vscmaster force-pushed the feat/MS-496-Prepayment-hook-callback-flutter branch from bcc8b8c to 6750249 Compare February 6, 2026 12:24
@ottuco ottuco deleted a comment from vscmaster Feb 6, 2026
@jab3z
Copy link
Contributor

jab3z commented Feb 6, 2026

@claude review this PR

@claude
Copy link

claude bot commented Feb 6, 2026

Claude finished @jab3z's task in 1m 57s —— View job


PR Review Complete

  • Read repository guidelines (CLAUDE.md)
  • Review changed files and understand implementation
  • Analyze code quality and identify issues
  • Provide comprehensive feedback

Summary

This PR implements a prepayment hook callback feature that allows merchant applications to verify payment information before processing. The implementation adds a CardVerificationResult type and a verifyPayment callback mechanism across Flutter, Android (Kotlin), and iOS (Swift) layers.

Overall Assessment: The implementation is functionally sound and follows a consistent pattern across platforms. However, there are several code quality issues, typos, and potential improvements that should be addressed.


Issues and Recommendations

🔴 Critical Issues

1. String concatenation using + operator (Dart)

  • Location: Sample/lib/feature/checkout/checkout_screen.dart:149
  • Issue: Using + for string concatenation is discouraged in Dart. Use string interpolation instead.
// Current:
"Cannot pay your order.\n" + "Please, check purchase information"

// Should be:
"Cannot pay your order.\nPlease, check purchase information"
// or
"Cannot pay your order.\n${'Please, check purchase information'}"

2. Typo in error message

  • Location: ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/CheckoutPlatformView.swift:374
  • Issue: "eror" should be "error"
// Line 374:
error.message ?? "Unknown eror"  // Should be "error"

3. Unused variable in checkout module

  • Location: Sample/lib/feature/checkout/checkout_module.dart:47
  • Issue: Parameter Result in _canPop is declared but never used
_canPop(BuildContext context, bool didPop, Result) async {  // Result parameter unused

Consider adding an underscore prefix _Result or removing if truly unnecessary.

4. Inconsistent naming convention

  • Location: Sample/lib/feature/checkout/checkout_screen.dart:149
  • Issue: Inconsistent casing in constant names (snake_case vs camelCase)
const _methodCheckoutHeight = "METHOD_CHECKOUT_HEIGHT"  // Good
const _method_verify_payment = "METHOD_VERIFY_PAYMENT"  // Should be _methodVerifyPayment

🟡 Code Quality Issues

5. Unnecessary default parameter value

  • Location: lib/src/card_verification_result.dart:8
  • Issue: null is already the default for optional parameters
// Current:
factory CardVerificationResult.success({T? value = null}) => Success(value);

// Should be:
factory CardVerificationResult.success({T? value}) => Success(value);

6. Inconsistent comparison operator

  • Location: Sample/lib/feature/checkout/checkout_module.dart:23
  • Issue: Type annotation is unnecessarily verbose
final Map<String,dynamic> extra = state.extra as Map<String, dynamic>;
// Missing space after comma in Map<String,dynamic>

7. Print statements in production code

  • Location: Multiple files
    • lib/src/checkout_widget.dart:34, 42, 47
    • Sample/lib/feature/checkout/checkout_module.dart:44, 49
  • Issue: Using print() instead of proper logging (Logger is available)
  • Recommendation: Replace with Logger calls or remove debug prints

8. Hardcoded strings in method channel

  • Location: Multiple platform files
  • Issue: Method channel names and method names are duplicated across Dart, Kotlin, and Swift
  • Recommendation: Consider extracting to a shared constants file or at minimum ensure all are documented

9. Inconsistent error handling in Swift

  • Location: ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/CheckoutPlatformView.swift:164
  • Issue: Force unwrapping with try! can crash the app
apiTransactionDetails = try! getApiTransactionDetails(td)
  • Recommendation: Use proper error handling with try? or do-catch

10. Unused import

  • Location: Sample/lib/feature/checkout/checkout_module.dart:1
  • Issue: import 'dart:ffi'; is imported but never used

11. Magic number in Kotlin

  • Location: android/src/main/kotlin/com/ottu/flutter/checkout/CheckoutView.kt:121
  • Issue: Magic number 40 in height calculation
val px = ((height + 40) / density).toInt()
  • Recommendation: Extract to a named constant with explanation

12. Potential memory leak in iOS

  • Location: ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/CheckoutPlatformView.swift:28
  • Issue: paymentViewController is marked as weak but checkout is not, which is good. However, verify retain cycles.

🟢 Minor Suggestions

13. Consider using sealed class pattern more consistently

  • The CardVerificationResult sealed class is well-designed
  • Consider if final class is necessary for Success and Failure or if they should just be regular classes

14. Method comment has typo

  • Location: lib/src/card_verification_result.dart:2
/**
 *  Results class encapsulates successful outcome with a value of type [T]
 *  or a failure with message.
 */

Should be "Result class" (singular) or "The Results class"

15. Generic type E for error is underutilized

  • The error type E in CardVerificationResult<T, E> is constrained to String in all usage
  • Consider either making it always String or documenting why it's generic

16. Async delay for controller attachment

  • Location: ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/CheckoutPlatformView.swift:202
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
    self.tryAttachController()
}
  • This retry mechanism with 50ms delay seems fragile. Consider using a proper wait mechanism or documenting why this is necessary.

17. Commented-out code

  • Location: ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/CheckoutPlatformView.swift:308, 314, 464
  • Recommendation: Remove commented-out code or add explanation for why it's kept

Testing Recommendations

  1. Test the verification callback failure scenarios - Ensure proper error propagation when merchant verification fails
  2. Test network timeout scenarios - The 2-second delay in checkout_screen.dart:145 is only for demo; verify real API calls handle timeouts
  3. Test memory management - Verify no retain cycles or leaks with the checkout widget lifecycle
  4. Test platform channel communication - Ensure proper error handling when Flutter/native communication fails

Positive Aspects

✅ Consistent implementation pattern across all three platforms (Flutter, Android, iOS)
✅ Good use of sealed classes for type-safe result handling
✅ Proper use of coroutines in Android and async/await in iOS
✅ Good separation of concerns between platform code and Flutter
✅ Comprehensive callback handling (success, error, cancel, verify)


Conclusion

The implementation is solid overall and achieves the stated objective of adding prepayment verification hooks. The main concerns are code quality issues (typos, print statements, magic numbers) rather than functional problems. These should be addressed before merging to maintain code quality standards.

Recommendation: Request changes to address critical issues (#1-4) and strongly consider fixing code quality issues (#5-12).


@vscmaster vscmaster force-pushed the feat/MS-496-Prepayment-hook-callback-flutter branch from 6750249 to 8402aea Compare February 10, 2026 15:38
@vscmaster
Copy link
Contributor Author

@claude review

@claude
Copy link

claude bot commented Feb 10, 2026

Claude finished @vscmaster's task in 2m 11s —— View job


Security-Focused PR Review Complete

  • Read repository security guidelines (CLAUDE.md)
  • Review all changed files for security issues
  • Apply security review framework (CHD exposure, channel security, logging, etc.)
  • Check code quality and best practices
  • Provide comprehensive feedback

Executive Summary

This PR adds a prepayment verification callback mechanism that allows merchant apps to validate payment information before processing. The implementation spans Flutter (Dart), Android (Kotlin), and iOS (Swift) layers.

Security Assessment:PASS - No CHD exposure or critical security violations
Code Quality: ⚠️ NEEDS IMPROVEMENT - Multiple code quality and logging issues
Recommendation: Request changes to address logging, error handling, and code quality issues before merge


Security Review (per CLAUDE.md Framework)

✅ A) Boundary Proof: "Wrapper Only"

Status: PASS

  • ✅ No PAN/CVV/expiry fields in Dart models
  • ✅ No card input widgets in Flutter
  • CardVerificationResult is generic and contains no CHD
  • ✅ Verification callback receives opaque payload: String? — merchant-controlled, not CHD
  • ✅ Sample app's verification logic is demonstration-only (2-second delay mock) and contains no CHD

New code maintains "wrapper only" boundary correctly.


⚠️ B) Platform Channels

Status: NEEDS ATTENTION

New channel added:

  • com.ottu.sample/checkout/payment/verify — dedicated channel for payment verification callback

Issues Found:

  1. Inconsistent naming convention (checkout_widget.dart:10)

    const _method_verify_payment = "METHOD_VERIFY_PAYMENT";  // snake_case
    const _methodOnWidgetDetached = "METHOD_ON_WIDGET_DETACHED";  // camelCase

    Fix inconsistent naming →

  2. Redundant null default (checkout_widget.dart:22)

    VerifyPaymentDelegate? verifyPayment = null  // null is already default
  3. Handler registration in wrong lifecycle (checkout_widget.dart:41)
    setMethodCallHandler is called in didChangeDependencies() which runs multiple times. Should be in initState() or use a flag to prevent duplicate registrations.

  4. Payload not validated
    Native code passes arbitrary String? payload to Flutter without size limits or sanitization. While not CHD, could be exploited for DoS.


🔴 C) Logging & Error Handling

Status: CRITICAL ISSUES

New logging violations added:

  1. Flutter SDK Layer (checkout_widget.dart:34,42,47)

    print("OttuCheckoutWidget.dispose()")  // Line 34
    print("CheckoutWidget, method call: ${call.method}")  // Line 42
    print("CheckoutWidget, verifyPayment, result: ${result.runtimeType}")  // Line 47

    ⚠️ Violates CLAUDE.md Section C: SDK layer must not use print() — sets bad example for merchants

  2. Sample App (checkout_module.dart:43,48)

    print("_onPopInvoked: $didPop, result: $result")  // Line 43
    print("canPop: $canPopSwipe, didPop: $didPop")  // Line 48

    Sample continues teaching unsafe patterns

  3. Android Layer (CheckoutView.kt:225,230)

    Log.i(TAG, "verifyPayment, success")  // Line 225
    Log.e(TAG, "verifyPayment, error")  // Line 230

    While these don't log payloads, they add to the existing 27+ unguarded Log statements already flagged

  4. iOS Layer (CheckoutPlatformView.swift:361)

    Logger.sdk.info("CheckoutPlatformView.verifyPaymentCallback")

    Uses structured logging (good) but still logs all callbacks

Recommendations:

  • Remove all print() statements from SDK lib/ directory
  • Use logger package only for internal debugging with guards
  • Sample app should demonstrate SECURE logging patterns (redaction, conditional compilation)

Fix logging issues in checkout_widget.dart →


✅ D) Persistence / Caching

Status: PASS

No storage added. PASS.


✅ E) UI & UX

Status: PASS

No sensitive data rendered in Flutter widgets. Verification result only shows success/failure via existing alert dialogs (already in Sample app).


✅ F) Networking

Status: PASS

No networking code added. Sample app's verification callback has a 2-second mock delay — acceptable for demo.


✅ G) Package / Supply Chain

Status: PASS (no changes)

No dependency changes.


⚠️ H) Example App Patterns

Status: NEEDS IMPROVEMENT

Good:

  • ✅ Shows proper usage of CardVerificationResult<T,E> sealed class
  • ✅ Demonstrates both success and failure paths
  • ✅ Checkbox UI for testing failure scenario is clear

Bad:

  • ❌ Raw payment result still logged via logger.d() in checkout_screen.dart:62,70,77
  • ❌ Full payload logged: logger.d("verifyPayment, payload: $payload") at line 142
  • print() statements instead of proper guards (checkout_module.dart:43,48)

Merchants copying this code will inherit insecure logging patterns.


🔴 I) Testing Requirements

Status: CRITICAL - NO TESTS

Zero tests exist for this PR or the entire codebase (per CLAUDE.md known state).

Required tests for this feature:

  1. Unit test: CardVerificationResult.success() and .failure() factories work correctly
  2. Unit test: Flutter channel handler throws PlatformException with correct code/message on failure
  3. Integration test: Verify Android verifyPayment coroutine suspension works
  4. Integration test: Verify iOS verifyPaymentCallback async/await bridge works
  5. Mock test: Ensure _verifyPayment callback is called before payment proceeds

Without tests, this payment-critical feature is unverifiable.


Code Quality Issues

🟡 Medium Priority

1. Unused import (checkout_module.dart:1)

import 'dart:ffi';  // Never used

2. Unused parameter (checkout_module.dart:42,46)

_onPopInvoked(BuildContext context, bool didPop, dynamic result) {  // result unused
  print("_onPopInvoked: $didPop, result: $result");
}

_canPop(BuildContext context, bool didPop) async {  // No parameter 'Result' in signature but docstring implies third parameter

Line 25 calls _canPop with 2 args but function at line 46 only accepts 2 args. Previous Claude review mentioned unused Result parameter — appears to have been removed but inconsistency remains.

3. Typo in error message (CheckoutPlatformView.swift:374)

error.message ?? "Unknown eror"  // Should be "error"

4. Magic number (CheckoutView.kt:123)

val height = ((height + 40) / density).toInt()  // Hardcoded 40

Should use defaultCheckoutPadding constant (defined at line 60).

5. Force unwrap (CheckoutPlatformView.swift:164)

apiTransactionDetails = try? getApiTransactionDetails(td)

Already uses try? but initialization at line 169 uses:

let transactionDetails: TransactionDetails? = try apiTransactionDetails?.transactionDetails

Should be try? not try (bare try outside do-catch).

6. String concatenation (checkout_screen.dart:149)

"Cannot pay your order.\nPlease, check purchase information"

This is actually CORRECT (not string concatenation with + as previous review stated) — uses \n escape sequence properly. No issue here.


🟢 Low Priority

7. KDoc comment (card_verification_result.dart:2)

/**
 *  Results class encapsulates...
 */

Should be "Result class" (singular) since it's a sealed class, not a collection.

8. Commented code (CheckoutPlatformView.swift:308,314,464)

//cht.savePhoneNumberIconColor = cc
//cht.selectorIconColor = cc  
//self.paymentViewController?.view.isHidden = true

Remove or document why kept.


Positive Aspects

Excellent sealed class designCardVerificationResult<T,E> with Success and Failure is type-safe and idiomatic
Consistent cross-platform implementation — same pattern in Dart, Kotlin (coroutines), and Swift (async/await)
Proper error propagation — Dart throws PlatformException, Kotlin returns CardVerificationResult.Failure, iOS resumes continuation with .failure
Non-blocking — All implementations use async properly (Kotlin suspend, iOS async, Dart Future)
Opaque payload — Verification receives String? without assuming structure, maintaining flexibility
Graceful fallbacknotImplemented() case returns failure message, doesn't crash


Recommendations Summary

Must Fix Before Merge (Critical)

  1. Remove print() statements from SDK layer (lib/src/checkout_widget.dart:34,42,47)
    Fix now →

  2. Fix method handler lifecycle — Move setMethodCallHandler to initState() or add guard
    Fix now →

  3. Fix constant naming — Rename _method_verify_payment to _methodVerifyPayment
    Fix now →

Should Fix (High Priority)

  1. Add unit tests for CardVerificationResult class
  2. Fix typo in Swift error message (line 374)
  3. Remove unused import dart:ffi from checkout_module.dart
  4. Use constant instead of magic number 40 in CheckoutView.kt:123

Consider (Medium Priority)

  1. Add payload size limit in native layer before passing to Flutter
  2. Remove commented code or add explanation
  3. Improve Sample app logging to demonstrate secure patterns

Conclusion

Functional Implementation: ✅ Solid — the prepayment verification feature works correctly across all platforms

Security Posture: ✅ Safe — no CHD exposure or critical security violations

Code Quality: ⚠️ Below Standard — excessive logging, lifecycle issues, missing tests

Verdict: REQUEST CHANGES

The PR is functionally complete and secure from a CHD perspective, but introduces poor logging practices that violate CLAUDE.md guidelines (Section C). The SDK layer must not use print() statements as this teaches merchants unsafe patterns.

Minimum changes required:

  1. Remove all print() statements from lib/src/checkout_widget.dart
  2. Fix method handler registration lifecycle
  3. Fix constant naming inconsistency
  4. Fix the typo in Swift error message

Once these are addressed, the PR will be ready to merge.


@vscmaster vscmaster merged commit efb397b into main Feb 10, 2026
1 check passed
@vscmaster vscmaster deleted the feat/MS-496-Prepayment-hook-callback-flutter branch February 11, 2026 15:30
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.

3 participants