Skip to content

Conversation

@erawat
Copy link
Member

@erawat erawat commented Nov 25, 2025

Overview

This PR adds generic payment processing infrastructure to support payment processor return flows (success/cancel URLs). It introduces two core services that centralize common payment logic previously duplicated across
payment processor extensions (Stripe, GoCardless, etc.).

Before

  • Payment processors had to implement their own contribution completion logic
  • No centralized customer ID management across payment processors
  • Each processor extension duplicated completion/failure/cancellation code
  • Risk of inconsistent behavior between different payment processors

After

  • ContributionCompletionService: Generic service for completing contributions with idempotency, automatic receipt detection, and fee recording
  • PaymentProcessorCustomerService: Generic customer ID management with database-backed storage preventing duplicate customer creation
  • PaymentProcessorCustomer entity: New entity (with APIv4 support) storing customer IDs for all payment processors
  • PaymentUrlBuilder.buildIpnUrl(): New utility method for building IPN callback URLs
  • Service container registration following established Hook/Container/ServiceContainer pattern

Technical Details

1. ContributionCompletionService (Civi/Paymentprocessingcore/Service/ContributionCompletionService.php)

  • Idempotent completion (safe to call multiple times)
  • Automatically detects receipt settings from contribution page
  • Records payment processor fees
  • Uses APIv4 for queries, APIv3 for Contribution.completetransaction
  • Comprehensive error handling with context via ContributionCompletionException

2. PaymentProcessorCustomerService (Civi/Paymentprocessingcore/Service/PaymentProcessorCustomerService.php)

  • Generic customer ID storage across all payment processors
  • getOrCreateCustomerId() method with callback pattern for creating customers on-demand
  • Prevents duplicate customer creation via database constraints
  • Supports CRUD operations: get, store, delete

3. PaymentProcessorCustomer Entity

  • New database table: civicrm_payment_processor_customer
  • Full APIv4 support via Civi\Api4\PaymentProcessorCustomer
  • Two unique constraints:
    • One customer per contact per processor
    • Processor customer IDs must be unique per processor
  • CASCADE delete when contact is deleted

4. Service Registration

  • Services registered via Hook/Container/ServiceContainer (following Stripe extension pattern)
  • Both services accessible via dependency injection:
    • \Civi::service('paymentprocessingcore.contribution_completion')
    • \Civi::service('paymentprocessingcore.payment_processor_customer')

Code Examples:

// Complete a contribution

  $service = \Civi::service('paymentprocessingcore.contribution_completion');
  $result = $service->complete(
    contributionId: 123,
    transactionId: 'ch_stripe_abc123',
    feeAmount: 2.50,
    sendReceipt: true
  );

// Get or create customer ID

  $customerService = \Civi::service('paymentprocessingcore.payment_processor_customer');
  $customerId = $customerService->getOrCreateCustomerId(
    contactId: 456,
    paymentProcessorId: 1,
    createCallback: function() use ($stripeApi) {
      return $stripeApi->customers->create(['email' => 'donor@example.org'])->id;
    }
  );

@erawat
Copy link
Member Author

erawat commented Nov 25, 2025

@codex review

@erawat erawat requested a review from Copilot November 25, 2025 17:00
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces generic payment processing infrastructure to support payment processor return flows (success/cancel URLs). The implementation centralizes common payment logic previously duplicated across payment processor extensions, providing two core services for contribution completion and customer ID management.

Key Changes:

  • Added ContributionCompletionService for idempotent contribution completion with automatic receipt detection and fee recording
  • Added PaymentProcessorCustomerService with database-backed customer ID storage to prevent duplicate customer creation
  • Created PaymentProcessorCustomer entity with full APIv4 support and unique constraints
  • Implemented service container registration following Hook/Container/ServiceContainer pattern
  • Added PaymentUrlBuilder.buildIpnUrl() utility method for building IPN callback URLs

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated no comments.

Show a summary per file
File Description
xml/schema/CRM/Paymentprocessingcore/PaymentProcessorCustomer.xml Defines database schema for PaymentProcessorCustomer entity with unique constraints and foreign keys
xml/schema/CRM/Paymentprocessingcore/PaymentProcessorCustomer.entityType.php Registers PaymentProcessorCustomer entity type for CiviCRM
Civi/Api4/PaymentProcessorCustomer.php Provides APIv4 interface for PaymentProcessorCustomer entity
CRM/Paymentprocessingcore/DAO/PaymentProcessorCustomer.php Auto-generated DAO class for database operations
CRM/Paymentprocessingcore/BAO/PaymentProcessorCustomer.php Business logic layer with helper methods for finding customers
Civi/Paymentprocessingcore/Service/PaymentProcessorCustomerService.php Service for managing customer IDs with get/create/store/delete operations
Civi/Paymentprocessingcore/Service/ContributionCompletionService.php Service for completing pending contributions with idempotency and receipt handling
Civi/Paymentprocessingcore/Exception/PaymentProcessorCustomerException.php Custom exception with context data for customer operations
Civi/Paymentprocessingcore/Exception/ContributionCompletionException.php Custom exception with context data for contribution completion
Civi/Paymentprocessingcore/Hook/Container/ServiceContainer.php Registers services for dependency injection
Civi/Paymentprocessingcore/Utils/PaymentUrlBuilder.php Added buildIpnUrl() method for IPN callback URLs
paymentprocessingcore.php Implements hook_civicrm_container for service registration
sql/auto_install.sql Creates PaymentProcessorCustomer table on installation
sql/auto_uninstall.sql Drops PaymentProcessorCustomer table on uninstallation
README.md Documentation for new services and usage examples
CRM/Paymentprocessingcore/Upgrader.php Removed commented example code
tests/phpunit/Civi/Api4/PaymentProcessorCustomerTest.php Comprehensive API tests for PaymentProcessorCustomer entity
tests/phpunit/Civi/Paymentprocessingcore/Service/PaymentProcessorCustomerServiceTest.php Unit tests for PaymentProcessorCustomerService
tests/phpunit/Civi/Paymentprocessingcore/Service/ContributionCompletionServiceTest.php Unit tests for ContributionCompletionService
tests/phpunit/Civi/Paymentprocessingcore/Utils/PaymentUrlBuilderTest.php Tests for PaymentUrlBuilder utility class
phpstan-baseline.neon PHPStan baseline updates for new code
docs/CONTRIBUTION-COMPLETION-SERVICE-IMPLEMENTATION-PLAN.md Detailed implementation plan documentation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@erawat erawat force-pushed the CIVIMM-417-return-flow-core branch from a29d070 to 1a92fe6 Compare November 25, 2025 18:03
### For Payment Processor Developers

The extension provides two main entities via API4:
The extension provides three main entities via API4:

Choose a reason for hiding this comment

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

I think just remove the number from here so you dont have to update it everytime

@erawat erawat merged commit 35b440d into master Nov 26, 2025
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.

3 participants