You've already forked Atomcms-edit
53f88b840a
- Replace flowframe/laravel-trend with direct Eloquent DB queries
- Replace srmklive/paypal (abandoned) with new PayPalService using PayPal REST API v2 via Guzzle
- Remove old config/paypal.php, migrate to config('habbo.paypal.*')
- Update blade templates to use habbo.paypal config
- Bump npm packages to latest: @inertiajs/react, axios, esbuild, eslint, sass, tailwindcss, etc.
- Run composer update and yarn upgrade
171 lines
6.0 KiB
PHP
Executable File
171 lines
6.0 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Http\Controllers\Shop;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Requests\AccountTopupFormRequest;
|
|
use App\Services\PayPalService;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class PayPalController extends Controller
|
|
{
|
|
private const string STATUS_CANCELLED = 'CANCELLED';
|
|
|
|
private const string STATUS_COMPLETED = 'COMPLETED';
|
|
|
|
private PayPalService $paypal;
|
|
|
|
public function __construct(PayPalService $paypal)
|
|
{
|
|
$this->paypal = $paypal;
|
|
}
|
|
|
|
public function process(AccountTopupFormRequest $request): Response|RedirectResponse
|
|
{
|
|
$amount = $request->integer('amount');
|
|
$orderData = [
|
|
'intent' => 'CAPTURE',
|
|
'application_context' => [
|
|
'return_url' => route('paypal.successful-transaction'),
|
|
'cancel_url' => route('paypal.cancelled-transaction'),
|
|
'brand_name' => setting('hotel_name'),
|
|
'landing_page' => 'BILLING',
|
|
'shipping_preference' => 'NO_SHIPPING',
|
|
'user_action' => 'CONTINUE',
|
|
],
|
|
'purchase_units' => [
|
|
[
|
|
'amount' => [
|
|
'currency_code' => config('habbo.paypal.currency'),
|
|
'value' => (string) $amount,
|
|
],
|
|
],
|
|
],
|
|
];
|
|
|
|
$response = $this->paypal->createOrder($orderData);
|
|
|
|
if ($response === null || ! isset($response['id'])) {
|
|
$message = $response['message'] ?? __('Something went wrong');
|
|
Log::error('Error creating order', ['response' => $response]);
|
|
|
|
return to_route('shop.index')->withErrors(['message' => $message]);
|
|
}
|
|
|
|
foreach ($response['links'] as $links) {
|
|
if ($links['rel'] === 'approve') {
|
|
$request->user()->transactions()->create([
|
|
'transaction_id' => $response['id'],
|
|
'amount' => 0,
|
|
'expected_amount' => $amount,
|
|
]);
|
|
|
|
return redirect()->away($links['href']);
|
|
}
|
|
}
|
|
|
|
return to_route('shop.index')->withErrors(
|
|
['message' => __('Something went wrong')],
|
|
);
|
|
}
|
|
|
|
public function successful(Request $request): Response|RedirectResponse
|
|
{
|
|
$request->validate([
|
|
'token' => 'required',
|
|
]);
|
|
|
|
$user = $request->user();
|
|
|
|
$transaction = $user->transactions()->where('transaction_id', $request['token'])->first();
|
|
if ($transaction === null) {
|
|
return to_route('shop.index')->withErrors(['message' => __('Something went wrong, please try again later')]);
|
|
}
|
|
|
|
if ($transaction->status === self::STATUS_COMPLETED) {
|
|
return to_route('shop.index')->withErrors(['message' => __('Transaction already processed')]);
|
|
}
|
|
|
|
$response = $this->paypal->capturePaymentOrder($request['token']);
|
|
|
|
if ($response === null) {
|
|
Log::error('PayPal capture returned null');
|
|
|
|
return to_route('shop.index')->withErrors(['message' => __('Something went wrong, please try again later')]);
|
|
}
|
|
|
|
if (isset($response['status'], $response['purchase_units'][0]['payments']['captures'][0]) === false) {
|
|
if (isset($response['error']['details'][0])) {
|
|
$details = $response['error']['details'][0];
|
|
$transaction->update([
|
|
'status' => $response['name'] ?? 'ERROR',
|
|
'description' => sprintf('%s - %s', $details['issue'] ?? '', $details['description'] ?? ''),
|
|
'amount' => 0,
|
|
]);
|
|
}
|
|
|
|
Log::error('Invalid response from PayPal', ['response' => $response]);
|
|
|
|
return to_route('shop.index')->withErrors(['message' => __('Something went wrong, please try again later')]);
|
|
}
|
|
|
|
$paymentDetails = $response['purchase_units'][0]['payments']['captures'][0];
|
|
$capturedAmount = (float) $paymentDetails['amount']['value'];
|
|
$expectedAmount = (float) ($transaction->expected_amount ?? 0);
|
|
|
|
if ($expectedAmount > 0 && abs($capturedAmount - $expectedAmount) > 0.01) {
|
|
Log::warning('PayPal amount mismatch', [
|
|
'transaction_id' => $transaction->transaction_id,
|
|
'expected' => $expectedAmount,
|
|
'captured' => $capturedAmount,
|
|
]);
|
|
|
|
$transaction->update([
|
|
'status' => 'AMOUNT_MISMATCH',
|
|
'description' => 'Captured amount does not match expected amount',
|
|
'amount' => $capturedAmount,
|
|
]);
|
|
|
|
return to_route('shop.index')->withErrors(['message' => __('Transaction amount mismatch detected')]);
|
|
}
|
|
|
|
$transaction->update([
|
|
'status' => $paymentDetails['status'],
|
|
'amount' => $capturedAmount,
|
|
'currency' => $paymentDetails['amount']['currency_code'],
|
|
]);
|
|
|
|
if ($response['status'] !== self::STATUS_COMPLETED) {
|
|
return to_route('shop.index')->withErrors(
|
|
['message' => __('Something went wrong')],
|
|
);
|
|
}
|
|
|
|
$user->increment('website_balance', $capturedAmount);
|
|
|
|
return to_route('shop.index')->with('success', __('Transaction successful'));
|
|
}
|
|
|
|
public function cancelled(Request $request): RedirectResponse
|
|
{
|
|
$request->validate([
|
|
'token' => 'required',
|
|
]);
|
|
|
|
$transaction = $request->user()->transactions()->where('transaction_id', $request['token'])->first();
|
|
if ($transaction !== null) {
|
|
$transaction->update([
|
|
'status' => self::STATUS_CANCELLED,
|
|
'description' => 'The user cancelled the transaction',
|
|
]);
|
|
}
|
|
|
|
return to_route('shop.index')->withErrors(
|
|
['message' => __('You have canceled the transaction')],
|
|
);
|
|
}
|
|
}
|