Files
zemailnator/app/Services/Payments/Providers/OxapayProvider.php
idevakk 27ac13948c feat: implement comprehensive multi-provider payment processing system
- Add unified payment provider architecture with contract-based design
  - Implement 6 payment providers: Stripe, Lemon Squeezy, Polar, Oxapay, Crypto, Activation Keys
  - Create subscription management with lifecycle handling (create, cancel, pause, resume, update)
  - Add coupon system with usage tracking and trial extensions
  - Build Filament admin resources for payment providers, subscriptions, coupons, and trials
  - Implement payment orchestration service with provider registry and configuration management
  - Add comprehensive payment logging and webhook handling for all providers
  - Create customer analytics dashboard with revenue, churn, and lifetime value metrics
  - Add subscription migration service for provider switching
  - Include extensive test coverage for all payment functionality
2025-11-19 09:37:00 -08:00

384 lines
12 KiB
PHP

<?php
namespace App\Services\Payments\Providers;
use App\Contracts\Payments\PaymentProviderContract;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class OxapayProvider implements PaymentProviderContract
{
protected array $config;
protected string $baseUrl;
public function __construct(array $config = [])
{
$this->config = $config;
$this->baseUrl = $config['sandbox'] ?? false
? 'https://api-sandbox.oxapay.com/v1'
: 'https://api.oxapay.com/v1';
}
public function getName(): string
{
return 'oxapay';
}
public function isActive(): bool
{
return ! empty($this->config['merchant_api_key']);
}
public function supportsRecurring(): bool
{
return false; // OxaPay doesn't support recurring payments
}
public function supportsOneTime(): bool
{
return true;
}
public function getSupportedCurrencies(): array
{
return Cache::remember('oxapay_currencies', now()->addHour(), function () {
try {
$response = Http::withHeaders([
'merchant_api_key' => $this->config['merchant_api_key'],
])->get("{$this->baseUrl}/info/currencies");
if ($response->successful()) {
$data = $response->json();
return $data['data'] ?? [];
}
} catch (\Exception $e) {
Log::error('Failed to fetch OxaPay currencies', [
'error' => $e->getMessage(),
]);
}
return ['BTC', 'ETH', 'USDT', 'USDC', 'LTC', 'BCH']; // Default common cryptos
});
}
public function calculateFees(float $amount): array
{
// OxaPay fees vary by currency and network
// Using average estimates - actual fees should be fetched from API
$percentageFee = 0.5; // 0.5% average
$fixedFee = 0.0; // No fixed fee for most cryptos
$totalFee = ($amount * $percentageFee / 100) + $fixedFee;
return [
'fixed_fee' => $fixedFee,
'percentage_fee' => $percentageFee,
'total_fee' => $totalFee,
'net_amount' => $amount - $totalFee,
];
}
public function createSubscription(User $user, Plan $plan, array $options = []): array
{
throw new \Exception('OxaPay does not support recurring subscriptions');
}
public function cancelSubscription(Subscription $subscription, string $reason = ''): bool
{
throw new \Exception('OxaPay does not support recurring subscriptions');
}
public function updateSubscription(Subscription $subscription, Plan $newPlan): array
{
throw new \Exception('OxaPay does not support recurring subscriptions');
}
public function pauseSubscription(Subscription $subscription): bool
{
throw new \Exception('OxaPay does not support recurring subscriptions');
}
public function resumeSubscription(Subscription $subscription): bool
{
throw new \Exception('OxaPay does not support recurring subscriptions');
}
public function getSubscriptionDetails(string $subscriptionId): array
{
throw new \Exception('OxaPay does not support recurring subscriptions');
}
public function createCheckoutSession(User $user, Plan $plan, array $options = []): array
{
try {
$amount = $options['amount'] ?? $plan->price;
$currency = $options['currency'] ?? 'USD';
$toCurrency = $options['to_currency'] ?? 'USDT';
$payload = [
'amount' => $amount,
'currency' => $currency,
'to_currency' => $toCurrency,
'lifetime' => $options['lifetime'] ?? 60, // 60 minutes default
'fee_paid_by_payer' => $options['fee_paid_by_payer'] ?? 0,
'callback_url' => $this->config['webhook_url'] ?? route('webhooks.oxapay'),
'return_url' => $this->config['success_url'] ?? route('payment.success'),
'email' => $user->email,
'order_id' => $options['order_id'] ?? null,
'description' => $options['description'] ?? "Payment for {$plan->name}",
'sandbox' => $this->config['sandbox'] ?? false,
];
$response = Http::withHeaders([
'merchant_api_key' => $this->config['merchant_api_key'],
'Content-Type' => 'application/json',
])->post("{$this->baseUrl}/payment/invoice", $payload);
if (! $response->successful()) {
throw new \Exception('Failed to create OxaPay invoice: '.$response->body());
}
$data = $response->json();
return [
'success' => true,
'checkout_url' => $data['data']['payment_url'] ?? null,
'payment_id' => $data['data']['track_id'] ?? null,
'expires_at' => $data['data']['expired_at'] ?? null,
'amount' => $amount,
'currency' => $currency,
'provider' => 'oxapay',
];
} catch (\Exception $e) {
Log::error('OxaPay checkout session creation failed', [
'error' => $e->getMessage(),
'user_id' => $user->id,
'plan_id' => $plan->id,
]);
return [
'success' => false,
'error' => $e->getMessage(),
];
}
}
public function createCustomerPortalSession(User $user): array
{
throw new \Exception('OxaPay does not provide customer portal functionality');
}
public function processWebhook(Request $request): array
{
try {
$payload = $request->getContent();
$signature = $request->header('HMAC');
if (! $this->validateWebhook($request)) {
throw new \Exception('Invalid webhook signature');
}
$data = $request->json()->all();
$status = $data['status'] ?? 'unknown';
$trackId = $data['track_id'] ?? null;
$type = $data['type'] ?? 'payment';
return [
'success' => true,
'event_type' => $status,
'provider_transaction_id' => $trackId,
'processed' => true,
'data' => $data,
'type' => $type,
];
} catch (\Exception $e) {
Log::error('OxaPay webhook processing failed', [
'error' => $e->getMessage(),
'payload' => $request->getContent(),
]);
return [
'success' => false,
'event_type' => 'error',
'provider_transaction_id' => null,
'processed' => false,
'error' => $e->getMessage(),
];
}
}
public function validateWebhook(Request $request): bool
{
try {
$payload = $request->getContent();
$signature = $request->header('HMAC');
$apiSecret = $this->config['merchant_api_key'];
if (empty($signature) || empty($apiSecret)) {
return false;
}
$expectedSignature = hash_hmac('sha512', $payload, $apiSecret);
return hash_equals($expectedSignature, $signature);
} catch (\Exception $e) {
Log::error('OxaPay webhook validation failed', [
'error' => $e->getMessage(),
]);
return false;
}
}
public function getConfiguration(): array
{
return $this->config;
}
public function syncSubscriptionStatus(Subscription $subscription): array
{
throw new \Exception('OxaPay does not support recurring subscriptions');
}
public function getPaymentMethodDetails(string $paymentId): array
{
try {
$response = Http::withHeaders([
'merchant_api_key' => $this->config['merchant_api_key'],
])->get("{$this->baseUrl}/payment/info", [
'track_id' => $paymentId,
]);
if ($response->successful()) {
$data = $response->json();
return [
'success' => true,
'details' => $data['data'] ?? [],
];
}
return [
'success' => false,
'error' => 'Payment not found',
];
} catch (\Exception $e) {
return [
'success' => false,
'error' => $e->getMessage(),
];
}
}
public function processRefund(string $paymentIntentId, float $amount, string $reason = ''): array
{
// OxaPay doesn't support traditional refunds in crypto
// Would need manual payout process
return [
'success' => false,
'error' => 'OxaPay refunds must be processed manually via payouts',
];
}
public function getTransactionHistory(User $user, array $filters = []): array
{
try {
$response = Http::withHeaders([
'merchant_api_key' => $this->config['merchant_api_key'],
])->get("{$this->baseUrl}/payment/history", array_merge([
'email' => $user->email,
], $filters));
if ($response->successful()) {
$data = $response->json();
return [
'success' => true,
'transactions' => $data['data'] ?? [],
];
}
return [
'success' => false,
'error' => 'Failed to fetch transaction history',
];
} catch (\Exception $e) {
return [
'success' => false,
'error' => $e->getMessage(),
];
}
}
public function getSubscriptionMetadata(Subscription $subscription): array
{
throw new \Exception('OxaPay does not support recurring subscriptions');
}
public function updateSubscriptionMetadata(Subscription $subscription, array $metadata): bool
{
throw new \Exception('OxaPay does not support recurring subscriptions');
}
public function startTrial(Subscription $subscription, int $trialDays): bool
{
throw new \Exception('OxaPay does not support recurring subscriptions');
}
public function applyCoupon(Subscription $subscription, string $couponCode): array
{
throw new \Exception('OxaPay does not support recurring subscriptions');
}
public function removeCoupon(Subscription $subscription): bool
{
throw new \Exception('OxaPay does not support recurring subscriptions');
}
public function getUpcomingInvoice(Subscription $subscription): array
{
throw new \Exception('OxaPay does not support recurring subscriptions');
}
public function retryFailedPayment(Subscription $subscription): array
{
throw new \Exception('OxaPay does not support recurring subscriptions');
}
public function canModifySubscription(Subscription $subscription): bool
{
return false; // No recurring support
}
public function getCancellationTerms(Subscription $subscription): array
{
return [
'immediate_cancellation' => true,
'refund_policy' => 'no_refunds_crypto',
'cancellation_effective' => 'immediately',
'billing_cycle_proration' => false,
];
}
public function exportSubscriptionData(Subscription $subscription): array
{
throw new \Exception('OxaPay does not support recurring subscriptions');
}
public function importSubscriptionData(User $user, array $subscriptionData): array
{
throw new \Exception('OxaPay does not support recurring subscriptions');
}
}