Server : LiteSpeed System : Linux server335.web-hosting.com 4.18.0-553.62.1.lve.el8.x86_64 #1 SMP Mon Jul 21 17:50:35 UTC 2025 x86_64 User : cardxfeb ( 2452) PHP Version : 8.1.34 Disable Function : NONE Directory : /proc/thread-self/root/proc/self/root/home/cardxfeb/ |
'messages' => [['role' => 'user', 'content' => $prompt]],
]);
if (!$response->ok()) {
Log::error('[LlmAnalyzer] API error: ' . $response->status() . ' - ' . $response->body());
return $this->fallbackResult($merged);
}
$text = $response->json('content.0.text', '');
// Strip markdown code blocks if present
$text = preg_replace('/^```(?:json)?\s*/m', '', $text);
$text = preg_replace('/\s*```$/m', '', $text);
$text = trim($text);
$data = json_decode($text, true);
if (json_last_error() !== JSON_ERROR_NONE) {
Log::warning('[LlmAnalyzer] Invalid JSON from LLM: ' . substr($text, 0, 200));
return $this->fallbackResult($merged);
}
// Sanitize template_id
$data['template_id'] = max(1, min(24, (int)($data['template_id'] ?? 1)));
// If no valid color from LLM, use detected brand color
if (empty($data['color_primary']) || !preg_match('/^#[0-9a-fA-F]{3,6}$/', $data['color_primary'])) {
$data['color_primary'] = $brandColors['theme_color']
?? $brandColors['css_primary']
?? '#2563EB';
}
return $data;
} catch (\Exception $e) {
Log::error('[LlmAnalyzer] Exception: ' . $e->getMessage());
return $this->fallbackResult($merged);
}
}
/**
* Legacy single-source analyze (backward compatibility)
*/
public function analyze(array $scrapedData, string $region = 'BR'): array
{
// Wrap single source as multi-source format
$wrapped = [
'social' => [$scrapedData],
'corporate' => [],
'merged' => [
'name' => $scrapedData['title'] ?? '',
'bio_hint' => $scrapedData['description'] ?? '',
'brand_name' => '',
'brand_desc' => '',
'brand_colors' => [],
'platforms' => [$scrapedData['platform'] ?? 'website'],
'sources' => 1,
],
];
return $this->analyzeMultiple($wrapped, $region);
}
private function fallbackResult(array $merged): array
{
$brandColor = $merged['brand_colors']['theme_color']
?? $merged['brand_colors']['css_primary']
?? '#2563EB';
return [
'name' => $merged['name'] ?? 'Meu Carto Digital',
'profession' => '',
'bio' => $merged['bio_hint'] ?? '',
'company' => $merged['brand_name'] ?? '',
'color_primary' => $brandColor,
'color_secondary' => '#F59E0B',
'template_id' => 1,
'links' => [],
'social_handles' => [],
'brand_personality' => 'profissional',
'confidence' => 'low',
];
}
}
'''
with open(f"{base}/app/Services/LlmAnalyzerService.php", 'w') as f:
f.write(llm)
print(f"UPGRADED: LlmAnalyzerService.php ({len(llm)} chars)")
# ============================================================
# 3. AiCardGeneratorService.php use fetchMultiple + analyzeMultiple
# ============================================================
generator = r'''<?php
namespace App\Services;
use App\Models\Vcard;
use App\Models\User;
use Illuminate\Support\Facades\Log;
class AiCardGeneratorService
{
public function __construct(
protected SocialScraperService $scraper,
protected LlmAnalyzerService $llm
) {}
/**
* Full AI generation pipeline with MULTI-SOURCE support.
* $sources can be a single URL string or an array of URLs.
*/
public function generate(User $user, string|array $sources, Vcard $vcard): array
{
// Normalize to array
if (is_string($sources)) {
$sources = [$sources];
}
$sources = array_filter($sources, fn($s) => !empty(trim($s)));
try {
// Step 1: Fetch from ALL sources in parallel (sequential for shared hosting)
Log::info("[AiCardGen] Fetching " . count($sources) . " sources for user#{$user->id}");
$multiSourceData = $this->scraper->fetchMultiple($sources);
$socialCount = count($multiSourceData['social'] ?? []);
$corporateCount = count($multiSourceData['corporate'] ?? []);
Log::info("[AiCardGen] Fetched: {$socialCount} social, {$corporateCount} corporate sources");
// Step 2: LLM multi-source analysis
$region = config('app.region', 'BR');
$aiResult = $this->llm->analyzeMultiple($multiSourceData, $region);
Log::info("[AiCardGen] LLM result: template_id={$aiResult['template_id']}, confidence={$aiResult['confidence']}");
// Step 3: Apply to VCard
$updateData = [
'ai_status' => 'completed',
'ai_data' => json_encode($aiResult, JSON_UNESCAPED_UNICODE),
'template_id' => $aiResult['template_id'],
];
// Only fill fields if VCard doesn't already have them
if (empty($vcard->name) && !empty($aiResult['name'])) {
$updateData['name'] = $aiResult['name'];
}
if (empty($vcard->description) && !empty($aiResult['bio'])) {
$updateData['description'] = $aiResult['bio'];
}
if (empty($vcard->occupation) && !empty($aiResult['profession'])) {
$updateData['occupation'] = $aiResult['profession'];
}
if (empty($vcard->company) && !empty($aiResult['company'])) {
$updateData['company'] = $aiResult['company'];
}
$vcard->update($updateData);
return [
'success' => true,
'vcard_id' => $vcard->id,
'template_id' => $aiResult['template_id'],
'completion_pct' => 90,
'sources_used' => count($sources),
'confidence' => $aiResult['confidence'] ?? 'medium',
'ai_result' => $aiResult,
];
} catch (\Exception $e) {
Log::error("[AiCardGen] Failed for user#{$user->id}: " . $e->getMessage());
$vcard->update(['ai_status' => 'failed']);
return ['success' => false, 'vcard_id' => $vcard->id, 'error' => $e->getMessage()];
}
}
}
'''
with open(f"{base}/app/Services/AiCardGeneratorService.php", 'w') as f:
f.write(generator)
print(f"UPGRADED: AiCardGeneratorService.php ({len(generator)} chars)")
# ============================================================
# 4. OnboardingController.php sources[] array support
# ============================================================
onboarding = r'''<?php
namespace App\Http\Controllers;
use App\Jobs\GenerateAiCardJob;
use App\Models\Vcard;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Rule;
class OnboardingController extends Controller
{
/** Show the onboarding form */
public function create()
{
return view('vcard.onboarding');
}
/**
* Store: accept 15 source URLs, create VCard skeleton, dispatch AI job.
* Input: sources[] array (or legacy social_url string)
*/
public function store(Request $request): \Illuminate\Http\RedirectResponse
{
// Support both legacy single URL and new multi-source array
if ($request->has('social_url') && !$request->has('sources')) {
$request->merge(['sources' => [$request->input('social_url')]]);
}
$request->validate([
'sources' => ['required', 'array', 'min:1', 'max:5'],
'sources.*' => ['required', 'url', 'max:500', 'distinct'],
], [
'sources.required' => 'Fornea pelo menos uma URL de rede social ou site.',
'sources.max' => 'Pode fornecer no mximo 5 fontes.',
'sources.*.url' => 'Uma das URLs fornecidas no vlida.',
'sources.*.distinct' => 'No repita URLs.',
]);
$user = Auth::user();
$sources = array_filter($request->sources, fn($s) => !empty(trim($s)));
// Determine primary URL for backward compatibility
$primaryUrl = $sources[0] ?? '';
// Create VCard skeleton
$vcard = Vcard::create([
'user_id' => $user->id,
'tenant_id' => $user->tenant_id ?? null,
'name' => $user->name,
'url_alias' => 'card-' . uniqid(),
'status' => 'pending_payment',
'ai_status' => 'pending',
'ai_social_url' => json_encode(array_values($sources), JSON_UNESCAPED_SLASHES),
'payment_deadline' => now()->addHours(24),
'template_id' => 1,
]);
Log::info("[Onboarding] VCard#{$vcard->id} created. Sources: " . implode(', ', $sources));
// Dispatch AI job with full sources array
GenerateAiCardJob::dispatch($user, $sources, $vcard->id)
->onQueue('ai-generation');
return redirect()->route('vcard.processing', ['vcard' => $vcard->id])
->with('message', count($sources) > 1
? 'A IA est a analisar ' . count($sources) . ' fontes para criar o seu carto...'
: 'A IA est a preparar o seu carto...'
);
}
/** Show processing screen */
public function processing(Vcard $vcard)
{
if ($vcard->user_id !== Auth::id()) {
abort(403);
}
return view('vcard.processing', compact('vcard'));
}
/** API: Poll AI status */
public function aiStatus(Vcard $vcard): JsonResponse
{
if ($vcard->user_id !== Auth::id()) {
return response()->json(['error' => 'Unauthorized'], 403);
}
$aiData = null;
if ($vcard->ai_status === 'completed' && $vcard->ai_data) {
$decoded = json_decode($vcard->ai_data, true);
$aiData = [
'template_id' => $decoded['template_id'] ?? null,
'color_primary' => $decoded['color_primary'] ?? null,
'confidence' => $decoded['confidence'] ?? null,
'brand_personality' => $decoded['brand_personality'] ?? null,
];
}
return response()->json([
'vcard_id' => $vcard->id,
'ai_status' => $vcard->ai_status,
'template_id' => $vcard->template_id,
'ai_data' => $aiData,
'redirect_url' => $vcard->ai_status === 'completed'
? route('user.vcard.edit', $vcard->id)
: null,
]);
}
}
'''
with open(f"{base}/app/Http/Controllers/OnboardingController.php", 'w') as f:
f.write(onboarding)
print(f"UPGRADED: OnboardingController.php ({len(onboarding)} chars)")
# ============================================================
# 5. GenerateAiCardJob.php support array sources
# ============================================================
job = r'''<?php
namespace App\Jobs;
use App\Models\Vcard;
use App\Models\User;
use App\Services\AiCardGeneratorService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class GenerateAiCardJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 3;
public int $timeout = 180; // 3 min multi-source takes longer
public int $backoff = 30;
public function __construct(
public User $user,
public string|array $sources, // single URL or array of URLs
public int $vcardId
) {}
public function handle(AiCardGeneratorService $aiService): void
{
$vcard = Vcard::findOrFail($this->vcardId);
$vcard->update(['ai_status' => 'processing']);
$sourcesCount = is_array($this->sources) ? count($this->sources) : 1;
Log::info("[GenerateAiCardJob] Starting: user#{$this->user->id}, vcard#{$this->vcardId}, {$sourcesCount} source(s)");
$result = $aiService->generate($this->user, $this->sources, $vcard);
if ($result['success']) {
Log::info("[GenerateAiCardJob] Done: template_id={$result['template_id']}, confidence={$result['confidence']}");
} else {
Log::error("[GenerateAiCardJob] Failed: " . ($result['error'] ?? 'unknown'));
$this->fail(new \Exception($result['error'] ?? 'AI generation failed'));
}
}
public function failed(\Throwable $exception): void
{
Vcard::find($this->vcardId)?->update(['ai_status' => 'failed']);
Log::error("[GenerateAiCardJob] Permanently failed: " . $exception->getMessage());
}
}
'''
with open(f"{base}/app/Jobs/GenerateAiCardJob.php", 'w') as f:
f.write(job)
print(f"UPGRADED: GenerateAiCardJob.php ({len(job)} chars)")
print("\nAll service upgrades complete!")
PYEOF
python3 /tmp/phase33_llm_onboarding.py
cat > /tmp/fix_csrf.py << 'PYEOF'
import os, re
base = os.path.expanduser('~/public_html')
# Find VerifyCsrfToken middleware
csrf_paths = [
f'{base}/app/Http/Middleware/VerifyCsrfToken.php',
f'{base}/bootstrap/app.php',
]
for p in csrf_paths:
if os.path.exists(p):
with open(p, 'r') as f:
content = f.read()
print(f'Found: {p}')
print(content[:600])
print('---')
PYEOF
python3 /tmp/fix_csrf.py
grep -n 'csrf\|VerifyCsrf\|webhook\|except' bootstrap/app.php app/Http/Middleware/*.php 2>/dev/null | head -30
cat app/Http/Middleware/VerifyCsrfToken.php
python3 -c "
import os
p = os.path.expanduser('~/public_html/app/Http/Middleware/VerifyCsrfToken.php')
with open(p,'r') as f: c = f.read()
if 'webhooks/brasil' not in c:
old = \"'/phonepe-Product-response'\"
new = old + \",\n // === CARD AO SEGUNDO CONTROL webhooks ===\n '/api/webhooks/brasil',\n '/api/webhooks/portugal',\n '/webhooks/brasil',\n '/webhooks/portugal'\"
c = c.replace(old, new, 1)
with open(p,'w') as f: f.write(c)
print('CSRF exceptions added')
else:
print('Already added')
"
php artisan config:clear && php artisan cache:clear && php artisan route:clear && php artisan view:clear && echo 'CACHE CLEARED'
python3 -c "
import re
p = '/home/cardxfeb/public_html/.env'
with open(p,'r') as f: c = f.read()
new_key = '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'
c2 = re.sub(r'CLAUDE_API_KEY=.*', 'CLAUDE_API_KEY=' + new_key, c)
if c2 != c:
with open(p,'w') as f: f.write(c2)
print('KEY UPDATED OK')
else:
print('KEY NOT FOUND - check .env')
"
php artisan config:clear && echo 'CONFIG CLEARED'
cd /home/cardxfeb/public_html && php artisan tinker --no-interaction 2>&1 &
cd /home/cardxfeb/public_html && php artisan tinker
python3 << 'PYEOF'
import re
# 1. Create HTTP validation file
token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJkb21haW4iOiJjYXJkLmFvc2VndW5kby5jb20uYnIiLCJleHAiOjE3Nzc2ODAwMDB9.nMfClhVIR-5_MO0NCaFuKzb-b12j2-yH2IaCtV2Qhnk'
with open('/home/cardxfeb/public_html/simpledcver.txt', 'w') as f:
f.write(token)
print('1. simpledcver.txt CRIADO OK')
# 2. Update ASAAS_WEBHOOK_TOKEN in .env
env_path = '/home/cardxfeb/public_html/.env'
with open(env_path, 'r') as f:
c = f.read()
wt = 'whsec_W8UvAs029jxRsq1LheBK3Z99FHE8M9AVapBRGIq4bFI'
c2 = re.sub(r'ASAAS_WEBHOOK_TOKEN=.*', 'ASAAS_WEBHOOK_TOKEN=' + wt, c)
if c2 != c:
with open(env_path, 'w') as f:
f.write(c2)
print('2. ASAAS_WEBHOOK_TOKEN ATUALIZADO OK')
else:
print('2. ASAAS_WEBHOOK_TOKEN nao encontrado - adicionando')
with open(env_path, 'a') as f:
f.write('\nASSAAS_WEBHOOK_TOKEN=' + wt + '\n')
print('2. ASAAS_WEBHOOK_TOKEN ADICIONADO OK')
# 3. Verify file is accessible
import os
size = os.path.getsize('/home/cardxfeb/public_html/simpledcver.txt')
print(f'3. Arquivo simpledcver.txt: {size} bytes')
PYEOF
curl -s https://cardfyz.com/simpledcver.txt | head -5
head -20 /home/cardxfeb/public_html/.htaccess
python3 << 'PYEOF'
# Add exception for simpledcver.txt in .htaccess
htpath = '/home/cardxfeb/public_html/.htaccess'
with open(htpath, 'r') as f:
c = f.read()
exception = '\n # Domain Validation File - do not remove\n RewriteCond %{REQUEST_URI} !^/simpledcver\.txt$\n'
# Insert before the main Laravel rewrite rule
if 'simpledcver' not in c:
c = c.replace(' RewriteCond %{REQUEST_FILENAME} !-d', exception + ' RewriteCond %{REQUEST_FILENAME} !-d')
with open(htpath, 'w') as f:
f.write(c)
print('HTACCESS UPDATED - exception added')
else:
print('ALREADY HAS EXCEPTION')
PYEOF
curl -s https://cardfyz.com/simpledcver.txt
python3 << 'PYEOF'
htpath = '/home/cardxfeb/public_html/.htaccess'
with open(htpath, 'r') as f:
c = f.read()
# Add rule at the very beginning to serve simpledcver.txt directly
rule = 'RewriteEngine On\nRewriteRule ^simpledcver\.txt$ simpledcver.txt [L]\n\n'
if 'simpledcver.txt [L]' not in c:
c = rule + c
with open(htpath, 'w') as f:
f.write(c)
print('RULE ADDED AT TOP')
else:
print('ALREADY EXISTS')
PYEOF
curl -s https://cardfyz.com/simpledcver.txt | head -3
tail -n 100 ~/public_html/storage/logs/laravel.log
cat ~/public_html/app/Http/Middleware/DetectRegion.php
head -n 30 ~/public_html/app/Http/Middleware/DetectRegion.php
sed -n '1,25p' ~/public_html/app/Http/Middleware/DetectRegion.php
sed -n '14,80p' ~/public_html/app/Http/Middleware/DetectRegion.php
sed -n '14,50p' ~/public_html/app/Http/Middleware/DetectRegion.php | cat -n
sed -i "s/'payment_gateway' => env('GATEWAY_BR', 'manual'),/'payment_gateway' => 'stripe_br',/g" ~/public_html/app/Http/Middleware/DetectRegion.php
grep -n 'env(' ~/public_html/app/Http/Middleware/DetectRegion.php
python3 -c "content = open('/home/cardxfeb/public_html/app/Http/Middleware/DetectRegion.php').read(); content = content.replace(\"'payment_gateway' => env('GATEWAY_BR', 'manual'),\", \"'payment_gateway' => 'stripe_br',\"); open('/home/cardxfeb/public_html/app/Http/Middleware/DetectRegion.php', 'w').write(content); print('Corrigido''')"
grep -n 'env(' ~/public_html/app/Http/Middleware/DetectRegion.php
sed -n '28,32p' ~/public_html/app/Http/Middleware/DetectRegion.php
php -l ~/public_html/app/Http/Middleware/DetectRegion.php
cd ~/public_html && php artisan config:clear && php artisan cache:clear && php artisan route:clear && php artisan view:clear
echo 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJkb21haW4iOiJjYXJkLmFvc2VndW5kby5wdCIsImFjY6MTc3NzY4MDAwMH0.cgNI_luwIx2vnV_uYufiQ-kcxJtE1f2KjDc9tjVFN5M' > ~/public_html/public/simpledcver.txt && echo 'Arquivo criado!'
host card.aosegundo.pt
host card.aosegundo.com.br && host aosegundo.pt
cd ~/public_html && php artisan tinker --execute="Mail::raw('Teste de e-mail do servidor CARDFYZ - OK', function(\$m) { \$m->to('geral@cardfyz.com')->subject('Teste de Envio - Card Ao Segundo'); });"
grep -E 'MAIL_|SMTP' ~/public_html/.env
python3 -c "content = open('/home/cardxfeb/public_html/.env').read(); content = content.replace('MAIL_HOST=mail.cardfyz.com', 'MAIL_HOST=server335.web-hosting.com'); open('/home/cardxfeb/public_html/.env', 'w').write(content); print('MAIL_HOST corrigido''')"
php artisan config:clear && php artisan tinker --execute="Mail::raw('Teste de e-mail do servidor CARDFYZ - OK', function(\$m) { \$m->to('geral@cardfyz.com')->subject('Teste de Envio - Card Ao Segundo'); });"
grep 'MAIL_HOST' ~/public_html/.env
cd ~/public_html && php artisan tinker --execute="Mail::raw('Teste CARDFYZ', function(\$m) { \$m->to('geral@cardfyz.com')->subject('Teste Cardfyz 2026'); }); echo 'OK';"
tail -n 80 ~/public_html/storage/logs/laravel.log | grep -A 20 'local.ERROR'
tail -n 50 ~/public_html/storage/logs/laravel.log
tail -n 200 ~/public_html/storage/logs/laravel.log | grep -B2 'sadmin\|admin\|ERROR' | head -n 60
cd ~/public_html && php artisan migrate:status 2>&1 | tail -n 30
php artisan tinker --execute="echo DB::select('SHOW COLUMNS FROM vcards LIKE \"deleted_at\"') ? 'Coluna existe' : 'Coluna NAO existe';"
php artisan make:migration add_soft_deletes_to_vcards_table --table=vcards
cat ~/public_html/database/migrations/2026_04_25_213715_add_soft_deletes_to_vcards_table.php
python3 << 'EOF'
content = open('/home/cardxfeb/public_html/database/migrations/2026_04_25_213715_add_soft_deletes_to_vcards_table.php').read()
content = content.replace(
"Schema::table('vcards', function (Blueprint $table) {\n //\n });",
"Schema::table('vcards', function (Blueprint $table) {\n $table->softDeletes();\n });",
1
)
content = content.replace(
"Schema::table('vcards', function (Blueprint $table) {\n //\n });",
"Schema::table('vcards', function (Blueprint $table) {\n $table->dropSoftDeletes();\n });",
1
)
open('/home/cardxfeb/public_html/database/migrations/2026_04_25_213715_add_soft_deletes_to_vcards_table.php', 'w').write(content)
print('Migration atualizada!')
EOF
cat ~/public_html/database/migrations/2026_04_25_213715_add_soft_deletes_to_vcards_table.php | grep -A5 'function up\|function down'
php artisan migrate --force 2>&1
ls -la ~/public_html/storage/framework/sessions/ | head -5 && stat -c '%a %n' ~/public_html/storage/framework/sessions/
cat ~/public_html/app/Http/Middleware/DetectRegion.php
sed -n '38,80p' ~/public_html/app/Http/Middleware/DetectRegion.php
sed -n '37,62p' ~/public_html/app/Http/Middleware/DetectRegion.php
wc -l ~/public_html/app/Http/Middleware/DetectRegion.php
python3 << 'PYEOF'
new_content = '''<?php
namespace App\\Http\\Middleware;
use Closure;
use Illuminate\\Http\\Request;
use Symfony\\Component\\HttpFoundation\\Response;
class DetectRegion
{
/**
* Mapa de dominios para configuracoes regionais
* CARD AO SEGUNDO CONTROL - Multi-Dominio
*/
protected array $regions = [
\'card.aosegundo.pt\' => [
\'currency\' => \'EUR\',
\'currency_symbol\' => \'\',
\'locale\' => \'pt_PT\',
\'country\' => \'PT\',
\'payment_gateway\' => \'ifthen\',
\'stripe_key\' => \'STRIPE_KEY_PT\',
\'stripe_secret\' => \'STRIPE_SECRET_PT\',
\'flag\' => \'PT\',
\'name\' => \'Portugal\',
],
\'card.aosegundo.com.br\' => [
\'currency\' => \'BRL\',
\'currency_symbol\' => \'R$\',
\'locale\' => \'pt_BR\',
\'country\' => \'BR\',
\'payment_gateway\' => \'asaas\',
\'stripe_key\' => \'STRIPE_KEY_BR\',
\'stripe_secret\' => \'STRIPE_SECRET_BR\',
\'flag\' => \'BR\',
\'name\' => \'Brasil\',
],
\'cardfyz.com\' => [
\'currency\' => \'EUR\',
\'currency_symbol\' => \'\',
\'locale\' => \'pt_BR\',
\'country\' => \'BR\',
\'payment_gateway\' => \'stripe_br\',
\'stripe_key\' => \'STRIPE_KEY\',
\'stripe_secret\' => \'STRIPE_SECRET\',
\'flag\' => \'BR\',
\'name\' => \'Card Ao Segundo (legado)\',
],
];
public function handle(Request $request, Closure $next): Response
{
$host = $request->getHost();
$region = $this->regions[$host] ?? $this->regions[\'cardfyz.com\'];
// Determinar gateway activo baseado no .env (dinamico - seguro dentro do handle)
if ($host === \'card.aosegundo.pt\') {
$activeGateway = env(\'GATEWAY_PT\', $region[\'payment_gateway\']);
} elseif ($host === \'card.aosegundo.com.br\') {
$activeGateway = env(\'GATEWAY_BR\', $region[\'payment_gateway\']);
} else {
$activeGateway = env(\'GATEWAY_BR\', $region[\'payment_gateway\']);
}
// Injectar configuracoes regionais em runtime
config([
\'app.currency\' => $region[\'currency\'],
\'app.currency_symbol\' => $region[\'currency_symbol\'],
\'app.locale\' => $region[\'locale\'],
\'app.country\' => $region[\'country\'],
\'app.payment_gateway\' => $activeGateway,
\'app.region_flag\' => $region[\'flag\'],
\'app.region_name\' => $region[\'name\'],
\'app.url\' => \'https://\' . $host,
]);
// Partilhar com todas as views Blade
view()->share(\'appRegion\', $region);
view()->share(\'appCurrency\', $region[\'currency\']);
view()->share(\'appCountry\', $region[\'country\']);
view()->share(\'currentDomain\', $host);
view()->share(\'activeGateway\', $activeGateway);
return $next($request);
}
}
'''
open('/home/cardxfeb/public_html/app/Http/Middleware/DetectRegion.php', 'w').write(new_content)
print('DetectRegion.php reescrito com sucesso!')
PYEOF
php -l ~/public_html/app/Http/Middleware/DetectRegion.php && php artisan config:clear && php artisan cache:clear && php artisan route:clear && php artisan view:clear
find ~ -name 'artisan' 2>/dev/null | head -20
ls /home/cardxfeb/public_html/app/Http/Controllers/ | grep -i 'front\|subscription\|home'
grep -n 'plan\|Plan\|currency\|currency_id' /home/cardxfeb/public_html/app/Http/Controllers/HomeController.php | head -40
grep -n 'choosePayment\|choose_payment\|paymentType\|currency\|BRL\|EUR' /home/cardxfeb/public_html/app/Http/Controllers/SubscriptionController.php | head -30
cd /home/cardxfeb/public_html && cat .env | grep -i 'app_name\|app_url\|app_env' | head -5
grep 'ASAAS' .env
sed -i 's/ASAAS_API_KEY=CONFIGURE_ME/ASAAS_API_KEY=597b4340-f667-41f2-940c-90f4c8cc1f43/' .env && sed -i 's/ASAAS_SANDBOX=true/ASAAS_SANDBOX=false/' .env && grep 'ASAAS' .env
find . -name '*saa*' -o -name '*Asaas*' 2>/dev/null | grep -v vendor | grep -v node_modules
grep -r 'asaas\|Asaas' app/Http/Controllers/ --include='*.php' -l 2>/dev/null
cat app/Http/Controllers/Webhooks/BrasilWebhookController.php
head -80 app/Http/Controllers/Webhooks/BrasilWebhookController.php
ls app/Http/Controllers/ && echo '---' && ls app/Http/Controllers/Webhooks/ 2>/dev/null
python3 -c "
conteudo = '''<?php
namespace App\\Http\\Controllers;
use App\\Models\\Plan;
use App\\Models\\User;
use Illuminate\\Http\\Request;
use Illuminate\\Support\\Facades\\Http;
use Illuminate\\Support\\Facades\\Auth;
use Illuminate\\Support\\Facades\\Log;
class AsaasController extends Controller
{
private string \$apiKey;
private string \$baseUrl;
public function __construct()
{
\$this->apiKey = env(\"ASAAS_API_KEY\");
\$this->baseUrl = env(\"ASAAS_BASE_URL\", \"https://api.asaas.com/v3\");
}
public function showCheckout(int \$planId)
{
\$plan = Plan::with(\"currency\")->findOrFail(\$planId);
if (\$plan->currency->currency_code !== \"BRL\") {
abort(403, \"Plano nao disponivel para esta regiao.\");
}
return view(\"asaas.checkout\", compact(\"plan\"));
}
public function processPayment(Request \$request, int \$planId)
{
\$request->validate([
\"cpf_cnpj\" => [\"required\", \"string\", \"min:11\", \"max:18\"],
\"name\" => [\"required\", \"string\"],
\"email\" => [\"required\", \"email\"],
]);
\$plan = Plan::with(\"currency\")->findOrFail(\$planId);
\$cpf = preg_replace(\"/\\D/\", \"\", \$request->cpf_cnpj);
\$customer = \$this->createOrFindCustomer(\$cpf, \$request->name, \$request->email);
return back()->withErrors([\"asaas\" => \"Erro cliente Asaas: \".json_encode(\$customer[\"errors\"] ?? [])]);
}
\$userId = Auth::id();
\$charge = \$this->createPixCharge(\$customer[\"id\"], \$plan->price, \"Plano {\$plan->name}\", \$planId, \$userId);
return back()->withErrors([\"asaas\" => \"Erro cobranca: \".json_encode(\$charge[\"errors\"] ?? [])]);
}
\$pixData = \$this->getPixQrCode(\$charge[\"id\"]);
return view(\"asaas.pix\", compact(\"plan\", \"charge\", \"pixData\"));
}
private function createOrFindCustomer(string \$cpfCnpj, string \$name, string \$email): array
{
\$search = Http::withHeaders([\"access_token\" => \$this->apiKey])
->get(\"\$this->baseUrl/customers\", [\"cpfCnpj\" => \$cpfCnpj]);
if (\$search->ok() && \$search->json(\"totalCount\") > 0) {
return \$search->json(\"data.0\");
}
return Http::withHeaders([\"access_token\" => \$this->apiKey])
->post(\"\$this->baseUrl/customers\", [\"name\" => \$name, \"email\" => \$email, \"cpfCnpj\" => \$cpfCnpj])
->json();
}
private function createPixCharge(string \$customerId, float \$value, string \$description, int \$planId, int \$userId): array
{
return Http::withHeaders([\"access_token\" => \$this->apiKey])
->post(\"\$this->baseUrl/payments\", [
\"customer\" => \$customerId,
\"billingType\" => \"PIX\",
\"value\" => \$value,
\"dueDate\" => now()->addDay()->format(\"Y-m-d\"),
\"description\" => \$description,
\"externalReference\" => \"plan_{\$planId}_user_{\$userId}\",
])->json();
}
private function getPixQrCode(string \$chargeId): array
{
return Http::withHeaders([\"access_token\" => \$this->apiKey])
->get(\"\$this->baseUrl/payments/{\$chargeId}/pixQrCode\")
->json();
}
}
'''
with open('app/Http/Controllers/AsaasController.php', 'w') as f:
f.write(conteudo)
print('AsaasController criado com sucesso')
"
cat > /tmp/create_asaas.py << 'PYEOF'
import os
content = """<?php
namespace App\\Http\\Controllers;
use App\\Models\\Plan;
use Illuminate\\Http\\Request;
use Illuminate\\Support\\Facades\\Http;
use Illuminate\\Support\\Facades\\Auth;
use Illuminate\\Support\\Facades\\Log;
class AsaasController extends Controller
{
private $apiKey;
private $baseUrl;
public function __construct()
{
$this->apiKey = env('ASAAS_API_KEY');
$this->baseUrl = env('ASAAS_BASE_URL', 'https://api.asaas.com/v3');
}
public function showCheckout($planId)
{
$plan = Plan::with('currency')->findOrFail($planId);
abort_unless($plan->currency->currency_code === 'BRL', 403);
return view('asaas.checkout', compact('plan'));
}
public function processPayment(Request $request, $planId)
{
$request->validate([
'cpf_cnpj' => ['required','string','min:11','max:18'],
'name' => ['required','string'],
'email' => ['required','email'],
]);
$plan = Plan::with('currency')->findOrFail($planId);
$cpf = preg_replace('/[^0-9]/', '', $request->cpf_cnpj);
$customer = $this->createOrFindCustomer($cpf, $request->name, $request->email);
if (!$customer || isset($customer['errors'])) {
return back()->withErrors(['asaas' => 'Erro cliente: '.json_encode($customer['errors'] ?? [])]);
}
$userId = Auth::id();
$charge = $this->createPixCharge($customer['id'], $plan->price, 'Plano '.$plan->name, $planId, $userId);
if (!$charge || isset($charge['errors'])) {
return back()->withErrors(['asaas' => 'Erro cobranca: '.json_encode($charge['errors'] ?? [])]);
}
$pixData = $this->getPixQrCode($charge['id']);
return view('asaas.pix', compact('plan', 'charge', 'pixData'));
}
private function createOrFindCustomer($cpfCnpj, $name, $email)
{
$search = Http::withHeaders(['access_token' => $this->apiKey])
->get($this->baseUrl.'/customers', ['cpfCnpj' => $cpfCnpj]);
if ($search->ok() && $search->json('totalCount') > 0) {
return $search->json('data')[0];
}
return Http::withHeaders(['access_token' => $this->apiKey])
->post($this->baseUrl.'/customers', ['name' => $name, 'email' => $email, 'cpfCnpj' => $cpfCnpj])
->json();
}
private function createPixCharge($customerId, $value, $description, $planId, $userId)
{
return Http::withHeaders(['access_token' => $this->apiKey])
->post($this->baseUrl.'/payments', [
'customer' => $customerId,
'billingType' => 'PIX',
'value' => $value,
'dueDate' => date('Y-m-d', strtotime('+1 day')),
'description' => $description,
'externalReference' => 'plan_'.$planId.'_user_'.$userId,
])->json();
}
private function getPixQrCode($chargeId)
{
return Http::withHeaders(['access_token' => $this->apiKey])
->get($this->baseUrl.'/payments/'.$chargeId.'/pixQrCode')
->json();
}
}
"""
with open('/home/cardxfeb/public_html/app/Http/Controllers/AsaasController.php', 'w') as f:
f.write(content)
print('OK: AsaasController.php criado')
PYEOF
python3 /tmp/create_asaas.py
mkdir -p resources/views/asaas && ls resources/views/ | grep asaas
ls resources/views/layouts/ | head -5
cat > /tmp/create_views.py << 'PYEOF'
checkout = """@extends('layouts.app')
@section('title', 'Checkout - Plano Brasil')
@section('content')
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">Assinar {{ $plan->name }}</h4>
</div>
<div class="card-body">
<p class="lead">Valor: <strong>R$ {{ number_format($plan->price, 2, ',', '.') }}/ms</strong></p>
<hr>
<form method="POST" action="{{ route('asaas.checkout.process', $plan->id) }}">
@csrf
<div class="mb-3">
<label class="form-label">Nome Completo *</label>
<input type="text" name="name" class="form-control @error('name') is-invalid @enderror" value="{{ auth()->user()->name ?? '' }}" required>
@error('name') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
<div class="mb-3">
<label class="form-label">E-mail *</label>
<input type="email" name="email" class="form-control @error('email') is-invalid @enderror" value="{{ auth()->user()->email ?? '' }}" required>
@error('email') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
<div class="mb-3">
<label class="form-label">CPF ou CNPJ * <small class="text-muted">(obrigatrio para Pix)</small></label>
<input type="text" name="cpf_cnpj" class="form-control @error('cpf_cnpj') is-invalid @enderror" placeholder="000.000.000-00" maxlength="18" required>
@error('cpf_cnpj') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
@if($errors->has('asaas'))
<div class="alert alert-danger">{{ $errors->first('asaas') }}</div>
@endif
<div class="d-grid">
<button type="submit" class="btn btn-success btn-lg">Gerar QR Code Pix</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
"""
pix = """@extends('layouts.app')
@section('title', 'Pague via Pix')
@section('content')
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6 text-center">
<div class="card shadow">
<div class="card-header bg-success text-white">
<h4 class="mb-0">Pague via Pix</h4>
</div>
<div class="card-body">
<p>Plano: <strong>{{ $plan->name }}</strong></p>
<p>Valor: <strong>R$ {{ number_format($plan->price, 2, ',', '.') }}</strong></p>
@if(isset($pixData['encodedImage']))
<img src="data:image/png;base64,{{ $pixData['encodedImage'] }}" alt="QR Code Pix" class="img-fluid" style="max-width:280px">
<div class="mt-3">
<label class="form-label"><small>Cdigo Copia e Cola:</small></label>
<textarea class="form-control" rows="3" onclick="this.select()" readonly>{{ $pixData['payload'] ?? '' }}</textarea>
</div>
@else
<div class="alert alert-warning">QR Code no disponvel. Tente novamente.</div>
@endif
<p class="mt-3 text-muted"><small>Aps o pagamento, seu plano ser ativado automaticamente.</small></p>
<a href="{{ route('admin.dashboard') }}" class="btn btn-outline-primary mt-2">Ir para o Painel</a>
</div>
</div>
</div>
</div>
</div>
@endsection
"""
with open('/home/cardxfeb/public_html/resources/views/asaas/checkout.blade.php', 'w') as f:
f.write(checkout)
print('OK: checkout.blade.php criado')
with open('/home/cardxfeb/public_html/resources/views/asaas/pix.blade.php', 'w') as f:
f.write(pix)
print('OK: pix.blade.php criado')
PYEOF
python3 /tmp/create_views.py
tail -20 routes/web.php
grep -n 'auth\|middleware.*auth\|role' routes/web.php | tail -10
wc -l routes/web.php
cat >> routes/web.php << 'ROUTESEOF'
// Asaas Checkout (Brasil/BRL)
use App\Http\Controllers\AsaasController;
Route::middleware(['auth', 'valid.user', 'role:admin'])->group(function () {
Route::get('/admin/asaas/checkout/{planId}', [AsaasController::class, 'showCheckout'])->name('asaas.checkout.show');
Route::post('/admin/asaas/checkout/{planId}', [AsaasController::class, 'processPayment'])->name('asaas.checkout.process');
});
ROUTESEOF
cat app/Http/Middleware/VerifyCsrfToken.php
sed -n '45,55p' app/Http/Controllers/HomeController.php
cp app/Http/Controllers/HomeController.php app/Http/Controllers/HomeController.php.bak && echo 'backup criado'
cat > /tmp/patch_home.py << 'PYEOF'
with open('app/Http/Controllers/HomeController.php', 'r') as f:
content = f.read()
old = """ $plans = Plan::with(['currency', 'planFeature', 'hasZeroPlan', 'planCustomFields'])->whereIsDefault(Plan::IS_DEACTIVE)->whereStatus(Plan::IS_ACTIVE)->get();"""
new = """ // Isolamento regional de planos por hostname
$host = request()->getHost();
$currencyCode = 'EUR'; // default Europa
if (str_contains($host, '.com.br')) { $currencyCode = 'BRL'; }
elseif (str_contains($host, '.pt')) { $currencyCode = 'EUR'; }
$plans = Plan::with(['currency', 'planFeature', 'hasZeroPlan', 'planCustomFields'])
->whereIsDefault(Plan::IS_DEACTIVE)
->whereStatus(Plan::IS_ACTIVE)
->whereHas('currency', function ($q) use ($currencyCode) {
$q->where('currency_code', $currencyCode);
})
->get();"""
if old in content:
content = content.replace(old, new)
with open('app/Http/Controllers/HomeController.php', 'w') as f:
f.write(content)
print('OK: HomeController.php atualizado com filtro regional')
else:
print('AVISO: Linha nao encontrada exatamente - verificar manualmente')
print('Trecho atual (linha 49):')
lines = content.split(chr(10))
for i, l in enumerate(lines[47:52], 48):
print(str(i) + ': ' + l)
PYEOF
python3 /tmp/patch_home.py
sed -n '83,115p' app/Http/Controllers/SubscriptionController.php
cp app/Http/Controllers/SubscriptionController.php app/Http/Controllers/SubscriptionController.php.bak && echo 'backup ok'
cat > /tmp/patch_sub.py << 'PYEOF'
with open('app/Http/Controllers/SubscriptionController.php', 'r') as f:
content = f.read()
old = " $subscriptionsPricingPlan = Plan::findOrFail($planId);"
new = " $subscriptionsPricingPlan = Plan::with('currency')->findOrFail($planId);"
"\n // Redirecionar planos BRL para o checkout Asaas"
"\n if ($subscriptionsPricingPlan->currency && $subscriptionsPricingPlan->currency->currency_code === 'BRL') {"
"\n return redirect()->route('asaas.checkout.show', $planId);"
"\n }"
if old in content:
content = content.replace(old, new)
with open('app/Http/Controllers/SubscriptionController.php', 'w') as f:
f.write(content)
print('OK: SubscriptionController.php atualizado - redirecionamento BRL para Asaas')
else:
print('AVISO: Linha nao encontrada. Verificar manualmente linha 109')
PYEOF
python3 /tmp/patch_sub.py
python3 << 'EOF'
content = open('app/Http/Controllers/SubscriptionController.php').read()
old = " $subscriptionsPricingPlan = Plan::findOrFail($planId);"
new_code = " $subscriptionsPricingPlan = Plan::with('currency')->findOrFail($planId);\n if ($subscriptionsPricingPlan->currency && $subscriptionsPricingPlan->currency->currency_code === 'BRL') {\n return redirect()->route('asaas.checkout.show', $planId);\n }"
if old in content:
open('app/Http/Controllers/SubscriptionController.php','w').write(content.replace(old,new_code))
print('OK: SubscriptionController atualizado')
else:
print('AVISO: nao encontrado')
EOF
php artisan config:clear && php artisan route:clear && php artisan view:clear && php artisan cache:clear && echo 'Cache limpo com sucesso'
php artisan route:list | grep asaas
cd /home/cardxfeb/public_html && curl -s -o /dev/null -w 'HTTP Status: %{http_code}\n' https://cardfyz.com/admin/asaas/checkout/7