Skip to content

Feat(history): Integrate payment history tracking into payment-processing contract#27

Open
onelove-dev wants to merge 4 commits into
PayStell:mainfrom
onelove-dev:evnt-history
Open

Feat(history): Integrate payment history tracking into payment-processing contract#27
onelove-dev wants to merge 4 commits into
PayStell:mainfrom
onelove-dev:evnt-history

Conversation

@onelove-dev

@onelove-dev onelove-dev commented Oct 4, 2025

Copy link
Copy Markdown

Closes #17

Integrate payment history tracking into payment-processing contract

  • Summary

    • Add payment record types, storage indices, and event module to the payment-processing contract
    • Embed payment history creation, status transitions, queries, and reconciliation
  • Testing

    • cargo test --package payment-processing-contract
    • stellar contract build
Screenshot 2025-10-04 at 7 55 58 AM

Summary by CodeRabbit

  • New Features

    • Payment history: fetch records by ID, merchant, or payer; query with filters and time windows.
    • Emits events for creation, status updates, completion, failure, and reconciliation.
    • Added payment validation and reconciliation operations.
    • Payment processing now returns a payment ID.
  • Improvements

    • Clearer, user-facing error messages for missing/failed record operations and invalid statuses.
  • Tests

    • Added tests and snapshots for history queries, validation, reconciliation, duplicate nonce handling, unsupported token, and successful payment.

@coderabbitai

coderabbitai Bot commented Oct 4, 2025

Copy link
Copy Markdown

Walkthrough

Adds payment history to the payment-processing contract: new types and errors, event definitions/emission, storage keys and CRUD/indexing, expanded public API (queries, validation, reconciliation), updated processing flow to create/update records and return payment_id, plus tests and snapshots reflecting the new state and events.

Changes

Cohort / File(s) Summary
Error variants
Contracts/payment-processing-contract/src/error.rs
Added four PaymentError variants and Display arms: PaymentRecordNotFound, PaymentRecordCreationFailed, PaymentRecordUpdateFailed, InvalidPaymentStatus.
Events module
Contracts/payment-processing-contract/src/events.rs
New event structs and an Events helper with emit methods for record creation, status updates, completion, failure, and reconciliation.
Contract API and flow
Contracts/payment-processing-contract/src/lib.rs
process_payment_with_signature now returns payment_id; integrates payment record lifecycle and event emission; adds query, validation, and reconciliation methods; introduces execute_payment helper.
Storage and indexing
Contracts/payment-processing-contract/src/storage.rs
New DataKey variants and storage APIs to create/update/get/query payment records, index by merchant/payer, generate IDs, and validate records.
Types
Contracts/payment-processing-contract/src/types.rs
Introduced PaymentStatus, PaymentRecord, QueryFilter, PaymentRecordQuery (contract types).
Tests
Contracts/payment-processing-contract/src/test.rs
Expanded tests for payment history, queries, validation, reconciliation; uses returned payment_id.
Test snapshots
Contracts/payment-processing-contract/test_snapshots/test/*
Added/updated JSON snapshots capturing new storage keys, payment records, histories, events, and reconciliation state across multiple scenarios.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant Ctr as PaymentProcessingContract
  participant S as Storage
  participant T as TokenClient
  participant E as Events

  C->>Ctr: process_payment_with_signature(order, sig, merchant_pk)
  activate Ctr
  Ctr->>S: generate_payment_id()
  S-->>Ctr: payment_id
  Ctr->>S: create_payment_record(Pending)
  S-->>Ctr: ok
  Ctr->>E: emit_payment_record_created(record)
  Ctr->>E: emit_payment_status_updated(Pending → Processing)
  Ctr->>S: update_payment_record(Processing)
  rect rgba(200,230,255,0.3)
    note over Ctr,T: Execute payment
    Ctr->>T: transfer(payer → merchant, amount, token)
    T-->>Ctr: ok / error
  end
  alt transfer ok
    Ctr->>S: update_payment_record(Completed)
    Ctr->>E: emit_payment_status_updated(Processing → Completed)
    Ctr->>E: emit_payment_completed(record)
    Ctr-->>C: payment_id
  else transfer error
    Ctr->>S: update_payment_record(Failed, error_message)
    Ctr->>E: emit_payment_status_updated(Processing → Failed, error)
    Ctr->>E: emit_payment_failed(payment_id, payer, merchant, error)
    Ctr-->>C: PaymentError(...)
  end
  deactivate Ctr
Loading
sequenceDiagram
  autonumber
  participant A as Admin/Caller
  participant Ctr as PaymentProcessingContract
  participant S as Storage
  participant E as Events

  A->>Ctr: reconcile_payments(payment_ids)
  activate Ctr
  loop for each payment_id
    Ctr->>S: validate_payment_record(payment_id)
    S-->>Ctr: bool
    alt inconsistent
      Ctr->>S: update_payment_record(...fix...)
    end
  end
  Ctr->>E: emit_payment_reconciliation(payment_ids, found, fixed)
  Ctr-->>A: fixed_count
  deactivate Ctr
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

  • PayStell/paystell-contracts#8 — Implements end-to-end payment history (records, statuses, storage/indexes, processing integration, queries, reconciliation, events), matching the introduced changes.

Poem

A ledger leaf rustles—PAY_1 sprouts anew,
I thump my paws: events hop into view.
From Pending to Done, with trails we can trace,
If burrows misalign, we reconcile the place.
Carrots tallied, tokens true—
Record by record, I audit the dew. 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.92% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly summarizes the primary change of integrating payment history tracking into the contract, matching the added types, storage, and event modules without listing files or extraneous details.
Linked Issues Check ✅ Passed The changes fully implement the objectives from issue #17 by extending the payment processing flow to create and update payment records atomically, emitting events for record creation, status changes, and reconciliation, adding storage and query APIs for consistency checks and repairs, introducing detailed error handling paths with new error variants, and optimizing history queries via indexed maps and batch operations.
Out of Scope Changes Check ✅ Passed All modifications across types, storage, lib, events, error handling, and tests directly support payment history tracking, querying, reconciliation, and event emission without introducing unrelated features or logic outside the scope of issue #17.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
Contracts/payment-processing-contract/src/test.rs (2)

455-496: LGTM! Basic validation test covers the happy path.

The test verifies that a successfully completed payment validates correctly.

Consider adding a test case for validation of a non-existent payment ID to ensure proper error handling:

#[test]
#[should_panic]
fn test_payment_validation_not_found() {
    let env = Env::default();
    let contract_id = env.register(PaymentProcessingContract {}, ());
    let client = PaymentProcessingContractClient::new(&env, &contract_id);
    
    // Try to validate non-existent payment
    let fake_payment_id = String::from_str(&env, "NONEXISTENT");
    client.validate_payment(&fake_payment_id);
}

498-545: LGTM! Reconciliation test covers the consistent payment scenario.

The test verifies that reconciling an already-consistent payment returns 0 fixes, which is the expected behavior.

Consider adding a test for the scenario where reconciliation actually fixes inconsistencies. This would require either:

  1. A helper function to manually corrupt a payment record's state
  2. A test that simulates a partial failure scenario

This would provide more comprehensive coverage of the reconciliation functionality.

Contracts/payment-processing-contract/src/types.rs (1)

25-34: Consider explicit discriminants for binary stability.

The PaymentStatus enum uses repr(u32) but relies on implicit discriminants. For a contract type that will be stored and potentially upgraded, explicit discriminants prevent accidental reordering from breaking compatibility.

Apply this diff to add explicit discriminants:

 #[contracttype]
 #[derive(Clone, Debug, PartialEq, Eq)]
 #[repr(u32)]
 pub enum PaymentStatus {
-    Pending,
-    Processing,
-    Completed,
-    Failed,
-    Cancelled,
+    Pending = 0,
+    Processing = 1,
+    Completed = 2,
+    Failed = 3,
+    Cancelled = 4,
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b70a4f6 and 4e86803.

📒 Files selected for processing (13)
  • Contracts/payment-processing-contract/src/error.rs (2 hunks)
  • Contracts/payment-processing-contract/src/events.rs (1 hunks)
  • Contracts/payment-processing-contract/src/lib.rs (5 hunks)
  • Contracts/payment-processing-contract/src/storage.rs (3 hunks)
  • Contracts/payment-processing-contract/src/test.rs (4 hunks)
  • Contracts/payment-processing-contract/src/types.rs (1 hunks)
  • Contracts/payment-processing-contract/test_snapshots/test/test_duplicate_nonce.1.json (2 hunks)
  • Contracts/payment-processing-contract/test_snapshots/test/test_payment_history_query_by_merchant.1.json (1 hunks)
  • Contracts/payment-processing-contract/test_snapshots/test/test_payment_history_query_by_payer.1.json (1 hunks)
  • Contracts/payment-processing-contract/test_snapshots/test/test_payment_reconciliation.1.json (1 hunks)
  • Contracts/payment-processing-contract/test_snapshots/test/test_payment_validation.1.json (1 hunks)
  • Contracts/payment-processing-contract/test_snapshots/test/test_successful_payment_with_signature.1.json (2 hunks)
  • Contracts/payment-processing-contract/test_snapshots/test/test_unsupported_token.1.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
Contracts/payment-processing-contract/src/lib.rs (2)
Contracts/payment-processing-contract/src/storage.rs (4)
  • get_payment_record (137-141)
  • get_merchant_payments (252-256)
  • get_payer_payments (258-262)
  • new (39-41)
Contracts/payment-processing-contract/src/events.rs (5)
  • emit_payment_record_created (61-72)
  • emit_payment_status_updated (74-89)
  • emit_payment_completed (91-101)
  • emit_payment_failed (103-118)
  • emit_payment_reconciliation (120-133)
Contracts/payment-processing-contract/src/storage.rs (1)
Contracts/payment-processing-contract/src/lib.rs (6)
  • get_payment_record (36-36)
  • get_payment_record (195-198)
  • get_merchant_payments (37-37)
  • get_merchant_payments (200-203)
  • get_payer_payments (38-38)
  • get_payer_payments (205-208)
🔇 Additional comments (8)
Contracts/payment-processing-contract/src/error.rs (1)

15-18: LGTM! Clear and consistent error additions.

The new error variants align well with the payment history tracking feature. The discriminants are sequential, messages are descriptive, and the Display implementation follows the existing pattern.

Also applies to: 31-34

Contracts/payment-processing-contract/src/test.rs (4)

8-11: LGTM! Necessary imports for expanded test coverage.

The addition of PaymentStatus, PaymentRecordQuery, and QueryFilter supports the new query and validation test cases.


117-145: LGTM! Comprehensive verification of payment record creation.

The test now properly captures the payment_id and verifies:

  • Payment record fields (payer, merchant, amount, status)
  • Completion timestamp
  • Payment appears in both merchant and payer histories

This ensures the payment history integration is working correctly.


286-368: LGTM! Thorough test of merchant query functionality.

The test properly verifies that querying by merchant returns all payments for that merchant, regardless of payer. The assertion logic correctly checks for both payment IDs in the results.


370-453: LGTM! Comprehensive test of payer query functionality.

The test properly verifies that querying by payer returns all payments made by that payer across different merchants. The test structure mirrors the merchant query test, providing consistent coverage.

Contracts/payment-processing-contract/src/types.rs (3)

36-51: LGTM! Well-designed payment record structure.

The PaymentRecord struct includes all necessary fields for tracking the payment lifecycle:

  • Comprehensive payment details (payer, merchant, amount, token, nonce, order_id)
  • Status tracking with timestamps (created_at, updated_at, completed_at)
  • Optional error_message for failure scenarios

The use of Option for completed_at and error_message is appropriate since these are only populated in specific scenarios.


53-58: LGTM! Clean and focused query filter design.

The QueryFilter enum supports the two primary query patterns (by merchant and by payer) as specified in the requirements. The structure is straightforward and extensible if additional filter types are needed in the future.


60-66: LGTM! Flexible query structure with timestamp filtering.

The PaymentRecordQuery struct combines the filter criteria with optional timestamp bounds, enabling:

  • Queries for all payments (no timestamp filter)
  • Queries from a specific time (from_timestamp only)
  • Queries within a time range (both timestamps)

This design supports the querying requirements effectively.

Comment on lines +95 to +191
// Generate payment ID
let payment_id = storage.generate_payment_id();

// Create initial payment record with Pending status
let mut payment_record = PaymentRecord {
payment_id: payment_id.clone(),
payer: payer.clone(),
merchant: order.merchant_address.clone(),
amount: order.amount,
token: order.token.clone(),
nonce: order.nonce,
order_id: order.order_id.clone(),
status: PaymentStatus::Pending,
created_at: current_time,
updated_at: current_time,
completed_at: None,
error_message: None,
};

// Create payment record atomically before processing
storage.create_payment_record(&payment_record)
.map_err(|_| PaymentError::PaymentRecordCreationFailed)?;

// Emit payment record created event
Events::emit_payment_record_created(&env, &payment_record);

// Update status to Processing
payment_record.status = PaymentStatus::Processing;
payment_record.updated_at = env.ledger().timestamp();
storage.update_payment_record(&payment_id, &payment_record)
.map_err(|_| PaymentError::PaymentRecordUpdateFailed)?;
Events::emit_payment_status_updated(
&env,
&payment_id,
PaymentStatus::Pending,
PaymentStatus::Processing,
None,
);

// Process payment with error handling
let processing_result = Self::execute_payment(
&env,
&payer,
&order,
&_signature,
&_merchant_public_key,
&storage,
);

match processing_result {
Ok(_) => {
// Update payment record to Completed
payment_record.status = PaymentStatus::Completed;
payment_record.updated_at = env.ledger().timestamp();
payment_record.completed_at = Some(env.ledger().timestamp());

storage.update_payment_record(&payment_id, &payment_record)
.map_err(|_| PaymentError::PaymentRecordUpdateFailed)?;

Events::emit_payment_status_updated(
&env,
&payment_id,
PaymentStatus::Processing,
PaymentStatus::Completed,
None,
);
Events::emit_payment_completed(&env, &payment_record);

Ok(payment_id)
}
Err(e) => {
// Update payment record to Failed with error message
payment_record.status = PaymentStatus::Failed;
payment_record.updated_at = env.ledger().timestamp();
let error_msg = String::from_str(&env, "Payment processing failed");
payment_record.error_message = Some(error_msg.clone());

// Try to update record, but don't fail if update fails
let _ = storage.update_payment_record(&payment_id, &payment_record);

Events::emit_payment_status_updated(
&env,
&payment_id,
PaymentStatus::Processing,
PaymentStatus::Failed,
payment_record.error_message.clone(),
);
Events::emit_payment_failed(
&env,
&payment_id,
&payer,
&order.merchant_address,
error_msg,
);

Err(e)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Failed payment history rolls back on error

We create and index the payment record, emit events, then return Err(e). In Soroban, returning Err aborts the invocation and reverts all earlier state writes, so the record/index updates never persist. You can see the fallout in test_duplicate_nonce where the events mention PAY_2, but the ledger still only contains PAY_1 because the call failed and the storage changes were rolled back. This violates the requirement to atomically create/update payment history and leaves observers with events that reference non-existent records.

We need to restructure the flow so storage commits survive failures—for example, run all fallible validations before inserting the record, and once the record exists, catch execute_payment errors, update the record to Failed, emit the failure events, and return Ok(payment_id) (or another non-reverting result that callers can inspect) instead of bubbling the Err. Only unrecoverable storage errors should abort before any writes. Adjusting the API if necessary to surface the failure context without reverting will keep history consistent.

@MPSxDev

MPSxDev commented Oct 7, 2025

Copy link
Copy Markdown
Contributor

@onelove-dev please solve potential issues and resolve conflicts. Thanks

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.

Integrate payment history with existing payment processing

2 participants