Skip to content

feat: add mock payment plugin and callback service#253

Open
ginaxu1 wants to merge 2 commits intoOpenNSW:mainfrom
ginaxu1:233-payment-plugin-callback-service
Open

feat: add mock payment plugin and callback service#253
ginaxu1 wants to merge 2 commits intoOpenNSW:mainfrom
ginaxu1:233-payment-plugin-callback-service

Conversation

@ginaxu1
Copy link
Contributor

@ginaxu1 ginaxu1 commented Mar 16, 2026

Closes #233

Screen.Recording.2026-03-24.at.13.53.10.mov

Summary

This PR introduces a secure, pluggable payment architecture to process gateway responses. Added the PaymentGateway interface to support multiple vendors (starting with GovPay) and a dedicated PaymentService to act as the domain orchestrator. Secure state transitions are guaranteed using an Outbound Fetch pattern, ensuring that task updates (IDLE → IN_PROGRESS → COMPLETED) only occur after direct server-to-server verification with the gateway provider.

Changes Made

backend/internal/task/plugin/gateway/ (Pluggable Architecture)

  • Define PaymentGateway interface in interface.go, separating untrusted data extraction (ExtractReference) from secure verification (GetPaymentInfo).
  • GovPay Adapter: implement GovPayProvider to encapsulate vendor-specific logic, including URL generation for the hosted checkout page and reference extraction from GovPay webhooks.
  • Add a thread-safe Registry to manage multiple gateway instances, allowing the system to switch providers dynamically based on the transaction context.

backend/internal/payment/ (Domain Orchestration)

  • Create a new PaymentService to house business logic previously residing in API handlers. It orchestrates the Outbound Fetch flow: receiving a raw webhook, extracting a reference, verifying it directly with the gateway's API, and updating the internal database
  • The service uses GORM DB transactions (s.db.Transaction) to update statuses and prevent race conditions. Upon successful verification, it calls tm.ExecuteTask to transition the internal FSM state securely

backend/internal/task/api/payment.go (Transport Layer)

  • Refactor PaymentHandler to be transport-only. It now purely extracts the {provider} from the URL path and delegates all processing to the PaymentService
  • Add HandleTransactionInquiry to support synchronous GET requests from the LankaPay network, delegating the final JSON schema formatting to the specific gateway adapter

backend/internal/task/plugin/payment.go (FSM & Plugin)

  • Payment FSM: implement the PaymentTask plugin with a finite state machine: IDLE ──(INITIATE_PAYMENT)──► IN_PROGRESS ──(PAYMENT_SUCCESS)──► COMPLETED.
  • Session Persistence: implement robust session management using readSession and WriteToLocalStore to persist the gateway URL and transaction metadata in the task's local store
  • Lazy Timeout Checking: add logic in GetRenderInfo to automatically transition tasks from IN_PROGRESS back to IDLE if a payment session exceeds its TTL, triggering a rotation of the payment session

backend/internal/app/bootstrap/ (System Wiring)

  • Service Registration: wire PaymentService into the application container and inject the TaskManager to enable the service to drive task state changes
  • Update WireManagers to register an upstream callback. This ensures that when the PaymentService completes a task, the WorkflowManager is automatically notified to progress the overall workflow

Portals (Trader App)

  • Update Payment.tsx to handle the INITIATE_PAYMENT action. It now correctly receives and handles the gatewayUrl for redirection to external payment portals like GovPay
  • URL Parameter Handling: implement a useEffect hook to detect the payment_error query parameter on return redirects, instantly alerting the user if an external transaction failed
  • State Propagation: update TaskDetailScreen.tsx to pass an onTaskUpdated callback to the plugin renderer, allowing the UI to refresh seamlessly upon mock or local payment completions

@gemini-code-assist

This comment was marked as outdated.

@ginaxu1 ginaxu1 marked this pull request as draft March 16, 2026 05:17
gemini-code-assist[bot]

This comment was marked as resolved.

gemini-code-assist[bot]

This comment was marked as resolved.

@ginaxu1 ginaxu1 force-pushed the 233-payment-plugin-callback-service branch from 1789404 to 983797d Compare March 16, 2026 07:05
gemini-code-assist[bot]

This comment was marked as outdated.

gemini-code-assist[bot]

This comment was marked as resolved.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new payment processing feature, including a PaymentConfig struct, a payment_transactions database table, and a PaymentHandler for processing payment callbacks and transaction inquiries. The backend now exposes programmatic ExecuteTask functionality and passes database and configuration to payment task plugins. The frontend integrates a mock payment screen and updates the Payment component to handle different payment methods and gateway redirections. Review feedback highlights several areas for improvement: the onTaskUpdated prop is no longer passed to the Payment component, which could lead to less smooth UI refreshes for instant card payments; the API key comparison in HandleTransactionInquiry should use a constant-time comparison to mitigate timing attacks; the "dummy" plugin instantiation for GetTransactionByReference could be refactored into a dedicated service for better decoupling; and the use of TaskID as ExecutionID in the payment_transactions table might not be granular enough to track individual payment attempts, especially in retry scenarios.

@ginaxu1 ginaxu1 force-pushed the 233-payment-plugin-callback-service branch 4 times, most recently from 807ba0e to 48df837 Compare March 16, 2026 07:49
@ginaxu1 ginaxu1 marked this pull request as ready for review March 16, 2026 07:56
@ginaxu1 ginaxu1 changed the title Add mock payment plugin and callback service feat: add mock payment plugin and callback service Mar 16, 2026
@ginaxu1 ginaxu1 force-pushed the 233-payment-plugin-callback-service branch from 48df837 to 5dc677c Compare March 17, 2026 04:54
gemini-code-assist[bot]

This comment was marked as resolved.

@ginaxu1 ginaxu1 force-pushed the 233-payment-plugin-callback-service branch 3 times, most recently from b2ba213 to 7c5080f Compare March 17, 2026 06:51
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a robust and extensible payment processing system, which is a significant feature. The use of a gateway interface and registry for different providers is excellent for future maintainability. The database-backed transaction handling with row-level locking in processPayment is well-implemented to ensure atomicity and prevent race conditions.

I've identified a couple of critical bugs related to database schema and state initialization that will cause runtime failures, and a medium-severity issue regarding hardcoded mock secrets. Please see the detailed comments for suggestions on how to address these points.

@ginaxu1 ginaxu1 force-pushed the 233-payment-plugin-callback-service branch from 7c5080f to 0afe442 Compare March 17, 2026 09:10
@ginaxu1
Copy link
Contributor Author

ginaxu1 commented Mar 18, 2026

nit: keep domain logic out of the HTTP handlers and TM

  1. GovPay sends a webhook (or an inquiry GET request) to /api/v1/payments/lankapay/callback
  2. api.PaymentHandler receives the HTTP request, extracts the {provider} and payload, and passes it to PaymentService.ProcessCallback()
  3. PaymentService uses the Registry to find the correct gateway provider, verifies the payload, and extracts the ReferenceNo
  4. PaymentService looks up the referenceNo in PaymentTransactionDB to find the associated TaskID
  5. PaymentService calls tm.ExecuteTask(ctx, req) with the TaskID and the success/failure action

@ginaxu1 ginaxu1 force-pushed the 233-payment-plugin-callback-service branch 4 times, most recently from 7572ce2 to 2498700 Compare March 18, 2026 12:41
@OpenNSW OpenNSW deleted a comment from gemini-code-assist bot Mar 18, 2026
@ginaxu1 ginaxu1 force-pushed the 233-payment-plugin-callback-service branch from 2498700 to 6147905 Compare March 18, 2026 14:08
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new payment processing system, integrating external payment gateways into the existing task management workflow. Key changes include adding a payment_transactions database table, new configuration for payment providers, and a payment.Service to manage callbacks and inquiries. The TaskManager and PaymentTask plugin were refactored to utilize a gateway.Registry and paymentRepository for persistent transaction management, moving away from local store history. The frontend Payment component was updated to support the new flow, including handling redirects to external gateways and passive polling for transaction status. Review comments highlight a critical inconsistency in payment status handling between the FSM and database, a missing validation for mandatory production payment secrets in the configuration, inconsistent environment variable naming for payment settings, and limited extensibility in the payment gateway selection logic.

@ginaxu1 ginaxu1 force-pushed the 233-payment-plugin-callback-service branch 2 times, most recently from 33f4c30 to edefe26 Compare March 19, 2026 05:21
@ginaxu1 ginaxu1 force-pushed the 233-payment-plugin-callback-service branch 3 times, most recently from 891b3e8 to 648beeb Compare March 24, 2026 08:20
@ginaxu1
Copy link
Contributor Author

ginaxu1 commented Mar 24, 2026

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a well-designed and secure payment architecture. The separation of concerns into a PaymentService, gateway Registry, and task Plugin is excellent. The use of an "Outbound Fetch" pattern, database transactions with row locking, and robust state management in the PaymentTask plugin demonstrates strong attention to security and reliability. The frontend changes are also well-implemented, especially the polling mechanism on the payment return screen.

I have one main point of feedback:

  1. A critical security stub in the GovPayProvider that bypasses payment verification. This must be addressed before production.

Overall, this is a very strong contribution. Addressing this point will make it ready for merging.

Comment on lines +99 to +108
func (p *GovPayProvider) GetPaymentInfo(ctx context.Context, referenceNumber string) (CallbackResult, error) {
// STUB: In production, this will make an outbound REST call to LankaPay's verification API
// using p.Secret and p.MerchantID as credentials.
// For now, we trust the webhook and return SUCCESS to allow development to proceed.
return CallbackResult{
ReferenceNumber: referenceNumber,
ProviderID: p.ID(),
Status: "SUCCESS",
}, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The GetPaymentInfo function is currently a stub that always returns a success result. This completely bypasses the "Outbound Fetch" security pattern described in the pull request, where the backend should make a secure server-to-server call to the payment gateway to verify the transaction status. Without this verification, the system is vulnerable to fraudulent payment confirmations. While this is acceptable for initial development, it's critical to implement the actual verification logic before this code reaches production. Please ensure a ticket is created to track the implementation of the real verification API call or HMAC signature validation.

mushrafmim and others added 2 commits March 24, 2026 20:06
…enNSW#257 (OpenNSW#265)

* feat: add ActionCard and ActionListView components for task management

* refactor: use static Tailwind classes in ActionCard to prevent purging OpenNSW#257

* feat: update ActionCard to use LockClosedIcon for locked state

* refactor: use Tailwind @Utility for custom scrollbar in index.css

* refactor: remove unused useState import from ActionCard component
@ginaxu1 ginaxu1 force-pushed the 233-payment-plugin-callback-service branch from 648beeb to be75bf5 Compare March 24, 2026 14:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement LankaPay Payment Plugin and Callback Service

2 participants