first commit
Some checks failed
Build / run (push) Has been cancelled

This commit is contained in:
maher
2025-10-29 11:42:25 +01:00
commit 703f50a09d
4595 changed files with 385164 additions and 0 deletions

View File

@@ -0,0 +1,163 @@
<?php namespace Common\Billing\Gateways\Paypal;
use App\Models\User;
use Common\Billing\Invoices\CreateInvoice;
use Common\Billing\Invoices\Invoice;
use Common\Billing\Models\Price;
use Common\Billing\Models\Product;
use Common\Billing\Subscription;
use Illuminate\Support\Carbon;
class PaypalSubscriptions
{
use InteractsWithPaypalRestApi;
public function isIncomplete(Subscription $subscription): bool
{
return $subscription->gateway_status === 'APPROVAL_PENDING' ||
$subscription->gateway_status === 'APPROVED';
}
public function isPastDue(Subscription $subscription): bool
{
// no way to check this via PayPal API
return false;
}
public function sync(
string $paypalSubscriptionId,
?int $userId = null,
): void {
$response = $this->paypal()->get(
"billing/subscriptions/$paypalSubscriptionId",
);
$price = Price::where('paypal_id', $response['plan_id'])->firstOrFail();
if ($userId != null) {
$user = User::where('id', $userId)->firstOrFail();
$user->update(['paypal_id' => $response['subscriber']['payer_id']]);
} else {
$user = User::where(
'paypal_id',
$response['subscriber']['payer_id'],
)->firstOrFail();
}
$subscription = $user->subscriptions()->firstOrNew([
'gateway_name' => 'paypal',
'gateway_id' => $response['id'],
]);
if (
in_array($response['status'], ['CANCELLED', 'EXPIRED', 'SUSPENDED'])
) {
$subscription->markAsCancelled();
}
$data = [
'price_id' => $price->id,
'product_id' => $price->product_id,
'gateway_name' => 'paypal',
'gateway_id' => $paypalSubscriptionId,
'gateway_status' => $response['status'],
'renews_at' =>
$response['status'] === 'ACTIVE' &&
isset($response['billing_info']['next_billing_time'])
? Carbon::parse(
$response['billing_info']['next_billing_time'],
)
: null,
];
if ($response['status'] === 'ACTIVE') {
$data['ends_at'] = null;
}
$subscription->fill($data)->save();
$this->createOrUpdateInvoice($subscription, $response->json());
}
public function createOrUpdateInvoice(
Subscription $subscription,
array $paypalSubscription,
): void {
// subscription is no longer active, no need to update invoice
if (!isset($paypalSubscription['billing_info']['next_billing_time'])) {
return;
}
$startTime = Carbon::parse($paypalSubscription['start_time']);
$renewsAt = Carbon::parse(
$paypalSubscription['billing_info']['next_billing_time'],
);
$isPaid = $paypalSubscription['status'] === 'ACTIVE';
$existing = Invoice::whereBetween('created_at', [
$startTime,
$renewsAt,
])->first();
if ($existing) {
// paid invoices should never be set to unpaid
if (!$existing->paid) {
$existing->update(['paid' => $isPaid]);
}
} else {
(new CreateInvoice())->execute([
'subscription_id' => $subscription->id,
'paid' => $isPaid,
]);
}
}
public function changePlan(
Subscription $subscription,
Product $newProduct,
Price $newPrice,
): bool {
$this->paypal()->post(
"billing/subscriptions/$subscription->gateway_id/revise",
[
'plan_id' => $newPrice->paypal_id,
],
);
$this->sync($subscription->gateway_id);
return true;
}
public function cancel(
Subscription $subscription,
$atPeriodEnd = true,
): bool {
if ($atPeriodEnd) {
$this->paypal()->post(
"billing/subscriptions/$subscription->gateway_id/suspend",
['reason' => 'User requested cancellation.'],
);
} else {
$this->paypal()->post(
"billing/subscriptions/$subscription->gateway_id/cancel",
['reason' => 'Subscription deleted locally.'],
);
}
$this->sync($subscription->gateway_id);
return true;
}
public function resume(Subscription $subscription, array $params): bool
{
$this->paypal()->post(
"billing/subscriptions/$subscription->gateway_id/activate",
['reason' => 'Subscription resumed by user.'],
);
$this->sync($subscription->gateway_id);
return true;
}
}