Skip to main content

onJ2CommerceGetSavedPaymentMethods Event

Overview

The onJ2CommerceGetSavedPaymentMethods event allows payment plugins to return structured payment method data for display in the unified "Payment Methods" tab in the customer's My Account profile.

When to Use

Implement this event when your payment plugin supports:

  • Saved credit/debit cards
  • Stored payment methods (wallets, bank accounts)
  • Customer payment profiles

Plugins that do NOT implement this event will continue using the legacy onJ2CommerceMyProfileTab and onJ2CommerceMyProfileTabContent events for separate tabs.

Event Registration

Add the event to your plugin's getSubscribedEvents() method:

public static function getSubscribedEvents(): array
{
return [
// ... other events ...
'onJ2CommerceGetSavedPaymentMethods' => 'onGetSavedPaymentMethods',
];
}

Event Handler Signature

public function onGetSavedPaymentMethods(Event $event): void
{
$userId = $event->getArgument('user_id', 0);

// Skip if not enabled or user not logged in
if ($userId < 1 || !(int) $this->params->get('allow_saved_cards', 1)) {
return;
}

// Get saved payment methods from your gateway
$methods = $this->getSavedMethodsFromGateway($userId);

// Build result array
$result = $event->getArgument('result', []);

foreach ($methods as $method) {
$result[] = [
'id' => $method->id,
'provider' => 'your_plugin_name',
'type' => 'card',
'display_name' => 'Visa ending in 4242',
'brand' => 'visa',
'last4' => '4242',
'exp_month' => 12,
'exp_year' => 2025,
'icon' => null, // Optional: custom icon URL
'is_default' => true,
'actions' => ['delete', 'set_default'],
'metadata' => [],
];
}

$event->setArgument('result', $result);
}

PaymentMethodData Structure

Return an array of associative arrays with these keys:

KeyTypeRequiredDescription
idstringYesUnique payment method identifier from your gateway
providerstringYesYour plugin identifier (lowercase, no spaces)
typestringYesPayment type: card, bank_account, wallet, etc.
display_namestringYesHuman-readable label (e.g., "Visa ending in 4242")
brandstringNoCard brand (visa, mastercard, amex, discover)
last4stringNoLast 4 digits of card/account
exp_monthintNoExpiration month (1-12)
exp_yearintNoExpiration year (4-digit)
iconstringNoCustom icon URL (uses default brand icon if null)
is_defaultboolYesWhether this is the customer's default payment method
actionsarrayYesAvailable actions: ['delete'], ['delete', 'set_default'], etc.
metadataarrayNoProvider-specific data (not displayed to user)

Brand Icon Resolution

The core component automatically provides brand icons for common card types:

BrandIcon Path
visamedia/com_j2commerce/images/payment-methods/visa.svg
mastercardmedia/com_j2commerce/images/payment-methods/mastercard.svg
amexmedia/com_j2commerce/images/payment-methods/amex.svg
discovermedia/com_j2commerce/images/payment-methods/discover.svg
(default)media/com_j2commerce/images/payment-methods/generic-card.svg

To provide a custom icon, set the icon key with a full URL path.

Provider Naming Convention

Use consistent lowercase identifiers:

Gatewayprovider value
Stripestripe
Authorize.netauthorizenet
PayTracepaytrace
Squaresquare
Braintreebraintree
PayPalpaypal

AJAX Actions

The unified Payment Methods tab uses your plugin's existing AJAX endpoints. Ensure these are implemented:

Delete Card

POST index.php?option=com_ajax&plugin={your_plugin}&task=deleteCard&format=json

Parameters:

  • {csrf_token}: Form token value
  • payment_method_id: The payment method ID to delete

Response:

{
"success": true
}
// or
{
"success": false,
"error": "Error message to display"
}

Set Default (Optional)

POST index.php?option=com_ajax&plugin={your_plugin}&task=setDefaultCard&format=json

Parameters:

  • {csrf_token}: Form token value
  • payment_method_id: The payment method ID to set as default

Example: Complete Implementation

<?php
declare(strict_types=1);

namespace Your\Plugin\J2Commerce\PaymentYourplugin\Extension;

use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;
use Joomla\Event\Event;

final class PaymentYourplugin extends CMSPlugin implements SubscriberInterface
{
protected $_name = 'payment_yourplugin';

public static function getSubscribedEvents(): array
{
return [
'onJ2CommerceGetPaymentPlugins' => 'onGetPaymentPlugins',
'onJ2CommerceGetPaymentOptions' => 'onGetPaymentOptions',
'onJ2CommercePrePayment' => 'onPrePayment',
'onJ2CommercePostPayment' => 'onPostPayment',
'onJ2CommerceGetSavedPaymentMethods' => 'onGetSavedPaymentMethods',
'onAjaxPayment_yourplugin' => 'onAjaxHandler',
];
}

public function onGetSavedPaymentMethods(Event $event): void
{
$userId = $event->getArgument('user_id', 0);

// Skip if feature disabled
if (!(int) $this->params->get('allow_saved_cards', 1)) {
return;
}

// Must have logged-in user
if ($userId < 1) {
return;
}

try {
// Get customer profile from your gateway
$customerProfile = $this->getCustomerProfile($userId);

if (!$customerProfile) {
return;
}

// Get payment methods from gateway
$paymentMethods = $this->fetchPaymentMethods($customerProfile);

$result = $event->getArgument('result', []);

foreach ($paymentMethods as $pm) {
$result[] = [
'id' => $pm->id,
'provider' => 'yourplugin',
'type' => 'card',
'display_name' => ucfirst($pm->brand) . ' ending in ' . $pm->last4,
'brand' => strtolower($pm->brand),
'last4' => $pm->last4,
'exp_month' => (int) $pm->expMonth,
'exp_year' => (int) $pm->expYear,
'icon' => null,
'is_default' => (bool) $pm->isDefault,
'actions' => ['delete', 'set_default'],
'metadata' => [
'customer_profile_id' => $customerProfile->id,
],
];
}

$event->setArgument('result', $result);

} catch (\Throwable $e) {
// Log error but don't throw - other plugins should still work
$this->logError('GetSavedPaymentMethods failed: ' . $e->getMessage());
}
}

public function onAjaxHandler(Event $event): void
{
$app = Factory::getApplication();
$task = $app->input->getCmd('task', '');
$user = $app->getIdentity();

// Authentication check
if (!$user || $user->guest) {
$event->setArgument('result', [json_encode([
'success' => false,
'error' => 'Not authenticated',
])]);
return;
}

// CSRF check
if (!Session::checkToken('request')) {
$event->setArgument('result', [json_encode([
'success' => false,
'error' => 'Invalid token',
])]);
return;
}

$result = match ($task) {
'deleteCard' => $this->deletePaymentMethod($user->id, $app->input->getString('payment_method_id')),
'setDefaultCard' => $this->setDefaultMethod($user->id, $app->input->getString('payment_method_id')),
default => ['success' => false, 'error' => 'Unknown task'],
};

$event->setArgument('result', [json_encode($result)]);
}

private function deletePaymentMethod(int $userId, string $methodId): array
{
// Implementation specific to your gateway
// ...

return ['success' => true];
}

private function setDefaultMethod(int $userId, string $methodId): array
{
// Implementation specific to your gateway
// ...

return ['success' => true];
}

// ... other methods
}

Backward Compatibility

If your plugin currently uses onJ2CommerceMyProfileTab and onJ2CommerceMyProfileTabContent:

  1. Implement onJ2CommerceGetSavedPaymentMethods for the unified tab
  2. Keep legacy handlers but have them return empty results
  3. The core will use the unified tab when any plugin provides data via the new event
// Legacy handlers (empty for unified tab)
public function onMyProfileTab(Event $event): void
{
// Return empty - using unified Payment Methods tab
}

public function onMyProfileTabContent(Event $event): void
{
// Return empty - using unified Payment Methods tab
}