Files
donations/controllers/DonationsController.php

798 lines
32 KiB
PHP

<?php
namespace humhub\modules\donations\controllers;
use humhub\modules\content\components\ContentContainerController;
use humhub\modules\content\components\ContentContainerControllerAccess;
use humhub\modules\donations\models\DonationGoal;
use humhub\modules\donations\models\DonationProviderConfig;
use humhub\modules\donations\models\DonationTransaction;
use humhub\modules\donations\permissions\Donate;
use humhub\modules\donations\services\DonationSettlementService;
use humhub\modules\donations\services\providers\PaymentGatewayService;
use humhub\modules\donations\services\providers\PayPalWebhookService;
use humhub\modules\donations\services\providers\StripeWebhookService;
use humhub\modules\space\models\Space;
use Yii;
use yii\filters\VerbFilter;
use yii\web\ForbiddenHttpException;
use yii\web\Response;
class DonationsController extends ContentContainerController
{
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['verbs'] = [
'class' => VerbFilter::class,
'actions' => [
'donate' => ['post'],
'stripe-webhook' => ['post'],
'paypal-webhook' => ['post'],
],
];
return $behaviors;
}
protected function getAccessRules()
{
return [[
ContentContainerControllerAccess::RULE_USER_GROUP_ONLY => [
Space::USERGROUP_OWNER,
Space::USERGROUP_ADMIN,
Space::USERGROUP_MODERATOR,
Space::USERGROUP_USER,
Space::USERGROUP_GUEST,
],
]];
}
public function beforeAction($action)
{
if (in_array($action->id, ['stripe-webhook', 'paypal-webhook'], true)) {
$this->enableCsrfValidation = false;
$this->detachBehavior('containerControllerBehavior');
}
return parent::beforeAction($action);
}
public function actionIndex()
{
$schemaReady = $this->isSchemaReady();
$goals = [];
$providerConfig = null;
if ($schemaReady) {
$goals = DonationGoal::find()
->where([
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
'is_active' => 1,
])
->orderBy(['id' => SORT_DESC])
->all();
$providerConfig = DonationProviderConfig::findOne([
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
]);
}
$dashboardData = $this->buildDonationDashboardData($schemaReady, $goals);
$canDonate = $this->canDonateInSpace();
return $this->render('index', [
'goals' => $goals,
'providerConfig' => $providerConfig,
'canDonate' => $canDonate,
'providerOptions' => $this->getEnabledProviderOptions($providerConfig),
'recurringOptions' => $this->getRecurringProviderKeys($providerConfig),
'space' => $this->contentContainer,
'schemaReady' => $schemaReady,
'dashboardData' => $dashboardData,
]);
}
public function actionDonate()
{
if (!$this->isSchemaReady()) {
$this->view->error(Yii::t('DonationsModule.base', 'Donations setup has not been run yet.'));
return $this->redirect($this->contentContainer->createUrl('/donations/donations/index'));
}
if (!$this->canDonateInSpace()) {
throw new ForbiddenHttpException('You are not allowed to donate in this space.');
}
$goalId = (int)Yii::$app->request->post('goal_id');
$goal = DonationGoal::findOne([
'id' => $goalId,
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
'is_active' => 1,
]);
if (!$goal instanceof DonationGoal) {
$this->view->error(Yii::t('DonationsModule.base', 'Donation goal not found.'));
return $this->redirect($this->contentContainer->createUrl('/donations/donations/index'));
}
$providerConfig = DonationProviderConfig::findOne([
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
]);
$provider = strtolower(trim((string)Yii::$app->request->post('provider', '')));
$mode = strtolower(trim((string)Yii::$app->request->post('mode', 'one_time')));
$amount = (float)Yii::$app->request->post('amount', 0);
$isAnonymous = (int)Yii::$app->request->post('is_anonymous', 0) === 1;
$enabledProviderOptions = $this->getEnabledProviderOptions($providerConfig);
if (!isset($enabledProviderOptions[$provider])) {
$this->view->error(Yii::t('DonationsModule.base', 'Selected provider is not enabled.'));
return $this->redirect($this->contentContainer->createUrl('/donations/donations/index'));
}
if (!in_array($mode, ['one_time', 'recurring'], true)) {
$this->view->error(Yii::t('DonationsModule.base', 'Invalid donation mode.'));
return $this->redirect($this->contentContainer->createUrl('/donations/donations/index'));
}
$recurringProviders = $this->getRecurringProviderKeys($providerConfig);
if ($mode === 'recurring' && !in_array($provider, $recurringProviders, true)) {
$this->view->error(Yii::t('DonationsModule.base', 'Selected provider does not have recurring enabled.'));
return $this->redirect($this->contentContainer->createUrl('/donations/donations/index'));
}
if ($amount <= 0) {
$this->view->error(Yii::t('DonationsModule.base', 'Please enter a valid donation amount.'));
return $this->redirect($this->contentContainer->createUrl('/donations/donations/index'));
}
$transaction = new DonationTransaction();
$transaction->contentcontainer_id = $this->contentContainer->contentcontainer_id;
$transaction->donor_user_id = Yii::$app->user->isGuest ? null : (int)Yii::$app->user->id;
$transaction->provider = $provider;
$transaction->mode = $mode;
$transaction->status = DonationTransaction::STATUS_PENDING;
$transaction->amount = $amount;
$transaction->currency = strtoupper((string)($providerConfig->default_currency ?? $goal->currency ?? 'USD'));
$transaction->is_anonymous = $isAnonymous ? 1 : 0;
$transaction->goal_id = (int)$goal->id;
$transaction->goal_type = (string)$goal->goal_type;
$transaction->target_animal_id = $goal->target_animal_id !== null ? (int)$goal->target_animal_id : null;
$transaction->metadata_json = json_encode([
'source' => 'mvp_intent_stub',
'goal_title' => $goal->title,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if (!$transaction->save()) {
$this->view->error(Yii::t('DonationsModule.base', 'Could not create donation intent. Please try again.'));
Yii::error($transaction->getErrors(), 'donations.intent.save');
return $this->redirect($this->contentContainer->createUrl('/donations/donations/index'));
}
$successUrl = Yii::$app->request->hostInfo . $this->contentContainer->createUrl('/donations/donations/complete', [
'id' => (int)$transaction->id,
]);
$cancelUrl = Yii::$app->request->hostInfo . $this->contentContainer->createUrl('/donations/donations/cancel', [
'id' => (int)$transaction->id,
]);
$gatewayService = new PaymentGatewayService();
$result = $gatewayService->createCheckout($transaction, $goal, $providerConfig, $successUrl, $cancelUrl);
$metadata = json_decode((string)$transaction->metadata_json, true);
if (!is_array($metadata)) {
$metadata = [];
}
$metadata['checkout_result'] = [
'success' => (bool)($result['success'] ?? false),
'provider' => $provider,
'mode' => $mode,
'checked_at' => date('c'),
'error' => (string)($result['error'] ?? ''),
'status' => (int)($result['status'] ?? 0),
];
$transaction->provider_checkout_id = (string)($result['checkout_id'] ?? $transaction->provider_checkout_id);
$transaction->provider_payment_id = (string)($result['payment_id'] ?? $transaction->provider_payment_id);
$transaction->provider_subscription_id = (string)($result['subscription_id'] ?? $transaction->provider_subscription_id);
$transaction->provider_customer_id = (string)($result['customer_id'] ?? $transaction->provider_customer_id);
$transaction->metadata_json = json_encode($metadata, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if (!(bool)($result['success'] ?? false)) {
$transaction->status = DonationTransaction::STATUS_FAILED;
$transaction->save(false);
$this->view->error(Yii::t('DonationsModule.base', 'Unable to start checkout: {message}', [
'message' => (string)($result['error'] ?? 'Unknown provider error.'),
]));
return $this->redirect($this->contentContainer->createUrl('/donations/donations/index'));
}
$transaction->save(false);
$redirectUrl = (string)($result['redirect_url'] ?? '');
if ($redirectUrl === '') {
$this->view->error(Yii::t('DonationsModule.base', 'Provider did not return a checkout URL.'));
return $this->redirect($this->contentContainer->createUrl('/donations/donations/index'));
}
return $this->redirect($redirectUrl);
}
public function actionComplete($id)
{
if (!$this->isSchemaReady()) {
return $this->redirect($this->contentContainer->createUrl('/donations/donations/index'));
}
$transaction = DonationTransaction::findOne([
'id' => (int)$id,
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
]);
if ($transaction instanceof DonationTransaction) {
$this->tryCapturePaypalOrderReturn($transaction);
$this->trySettlePaypalRecurringReturn($transaction);
if ($transaction->status === DonationTransaction::STATUS_PENDING) {
$this->view->info(Yii::t('DonationsModule.base', 'Checkout completed. Awaiting webhook confirmation from {provider}.', [
'provider' => strtoupper((string)$transaction->provider),
]));
} elseif ($transaction->status === DonationTransaction::STATUS_SUCCEEDED) {
$this->view->success(Yii::t('DonationsModule.base', 'Thank you. Your donation has been confirmed.'));
} else {
$this->view->info(Yii::t('DonationsModule.base', 'Checkout returned with status: {status}.', [
'status' => (string)$transaction->status,
]));
}
}
return $this->redirect($this->contentContainer->createUrl('/donations/donations/index'));
}
private function tryCapturePaypalOrderReturn(DonationTransaction $transaction): void
{
if ($transaction->status !== DonationTransaction::STATUS_PENDING) {
return;
}
if ($transaction->provider !== 'paypal' || $transaction->mode !== 'one_time') {
return;
}
$expectedOrderId = trim((string)$transaction->provider_checkout_id);
if ($expectedOrderId === '') {
return;
}
$returnToken = trim((string)Yii::$app->request->get('token', ''));
if ($returnToken === '' || $returnToken !== $expectedOrderId) {
return;
}
$providerConfig = DonationProviderConfig::findOne([
'contentcontainer_id' => $transaction->contentcontainer_id,
]);
if (!$providerConfig instanceof DonationProviderConfig || (int)$providerConfig->paypal_enabled !== 1) {
return;
}
$gatewayService = new PaymentGatewayService();
$captureResult = $gatewayService->capturePayPalOrder($transaction, $providerConfig);
if (!($captureResult['success'] ?? false)) {
$metadata = json_decode((string)$transaction->metadata_json, true);
if (!is_array($metadata)) {
$metadata = [];
}
$metadata['paypal_capture_result'] = [
'success' => false,
'checked_at' => date('c'),
'status' => (int)($captureResult['status'] ?? 0),
'error' => (string)($captureResult['error'] ?? 'PayPal order capture failed.'),
];
$transaction->metadata_json = json_encode($metadata, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$transaction->save(false);
Yii::warning([
'transaction_id' => (int)$transaction->id,
'status' => (int)($captureResult['status'] ?? 0),
'error' => (string)($captureResult['error'] ?? ''),
], 'donations.paypal.capture');
return;
}
$transaction->provider_payment_id = (string)($captureResult['payment_id'] ?? $transaction->provider_payment_id);
$settlement = new DonationSettlementService();
$settlement->markSucceededAndApply($transaction, [
'paypal_order_captured_at' => date('c'),
'paypal_order_already_captured' => !empty($captureResult['already_captured']) ? 1 : 0,
]);
}
private function trySettlePaypalRecurringReturn(DonationTransaction $transaction): void
{
if ($transaction->status !== DonationTransaction::STATUS_PENDING) {
return;
}
if ($transaction->provider !== 'paypal' || $transaction->mode !== 'recurring') {
return;
}
$expected = trim((string)$transaction->provider_checkout_id);
if ($expected === '') {
return;
}
$subscriptionId = trim((string)Yii::$app->request->get('subscription_id', ''));
$token = trim((string)Yii::$app->request->get('token', ''));
if ($subscriptionId !== $expected && $token !== $expected) {
return;
}
$settlement = new DonationSettlementService();
$settlement->markSucceededAndApply($transaction, [
'paypal_return_confirmed_at' => date('c'),
]);
}
public function actionCancel($id)
{
if ($this->isSchemaReady()) {
$transaction = DonationTransaction::findOne([
'id' => (int)$id,
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
]);
if ($transaction instanceof DonationTransaction && $transaction->status === DonationTransaction::STATUS_PENDING) {
$transaction->status = DonationTransaction::STATUS_CANCELLED;
$transaction->save(false);
}
}
$this->view->info(Yii::t('DonationsModule.base', 'Donation checkout was canceled.'));
return $this->redirect($this->contentContainer->createUrl('/donations/donations/index'));
}
private function getEnabledProviderOptions(?DonationProviderConfig $providerConfig): array
{
$options = [];
if ($providerConfig instanceof DonationProviderConfig && (int)$providerConfig->paypal_enabled === 1) {
$options['paypal'] = Yii::t('DonationsModule.base', 'PayPal');
}
if ($providerConfig instanceof DonationProviderConfig && (int)$providerConfig->stripe_enabled === 1) {
$options['stripe'] = Yii::t('DonationsModule.base', 'Stripe');
}
return $options;
}
private function getRecurringProviderKeys(?DonationProviderConfig $providerConfig): array
{
$providers = [];
if ($providerConfig instanceof DonationProviderConfig && (int)$providerConfig->paypal_enabled === 1 && (int)$providerConfig->paypal_recurring_enabled === 1) {
$providers[] = 'paypal';
}
if ($providerConfig instanceof DonationProviderConfig && (int)$providerConfig->stripe_enabled === 1 && (int)$providerConfig->stripe_recurring_enabled === 1) {
$providers[] = 'stripe';
}
return $providers;
}
private function isSchemaReady(): bool
{
return Yii::$app->db->schema->getTableSchema(DonationGoal::tableName(), true) !== null
&& Yii::$app->db->schema->getTableSchema(DonationProviderConfig::tableName(), true) !== null
&& Yii::$app->db->schema->getTableSchema(DonationTransaction::tableName(), true) !== null;
}
private function canDonateInSpace(): bool
{
if ($this->contentContainer->can(Donate::class)) {
return true;
}
if ($this->contentContainer instanceof Space && $this->contentContainer->isAdmin()) {
return true;
}
if ($this->contentContainer instanceof Space) {
$group = $this->contentContainer->getUserGroup(Yii::$app->user->getIdentity());
return in_array($group, [
Space::USERGROUP_OWNER,
Space::USERGROUP_ADMIN,
Space::USERGROUP_MODERATOR,
Space::USERGROUP_MEMBER,
Space::USERGROUP_USER,
Space::USERGROUP_GUEST,
], true);
}
return false;
}
private function buildDonationDashboardData(bool $schemaReady, array $goals): array
{
$result = [
'isManager' => $this->canManageDonationDashboard(),
'months' => [],
'selectedMonth' => '',
'userRows' => [],
'userMonthlyHeaders' => [],
'userGrandTotal' => 0.0,
'adminRows' => [],
'adminTotals' => ['target' => 0.0, 'donated' => 0.0, 'percent' => 0.0],
'ytd' => ['year' => (int)date('Y'), 'donated' => 0.0, 'target' => 0.0, 'percent' => 0.0],
'previousYear' => null,
];
if (!$schemaReady) {
return $result;
}
$transactions = DonationTransaction::find()
->where([
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
'status' => DonationTransaction::STATUS_SUCCEEDED,
])
->orderBy(['created_at' => SORT_DESC, 'id' => SORT_DESC])
->all();
if (empty($transactions)) {
return $result;
}
$goalById = [];
$goalByAnimalId = [];
foreach ($goals as $goal) {
if (!$goal instanceof DonationGoal) {
continue;
}
$goalById[(int)$goal->id] = $goal;
if ((string)$goal->goal_type === DonationGoal::TYPE_ANIMAL && (int)$goal->target_animal_id > 0) {
$goalByAnimalId[(int)$goal->target_animal_id] = $goal;
}
}
$availableMonths = [];
$donationByAnimalMonth = [];
$donationByAnimalYear = [];
$allAnimalIds = [];
foreach ($transactions as $transaction) {
$animalId = (int)($transaction->target_animal_id ?? 0);
if ($animalId <= 0 && (int)($transaction->goal_id ?? 0) > 0) {
$goal = $goalById[(int)$transaction->goal_id] ?? null;
if ($goal instanceof DonationGoal) {
$animalId = (int)($goal->target_animal_id ?? 0);
}
}
if ($animalId <= 0) {
continue;
}
$createdAt = trim((string)($transaction->created_at ?? ''));
$timestamp = $createdAt !== '' ? strtotime($createdAt) : false;
if ($timestamp === false) {
continue;
}
$amount = max(0.0, (float)($transaction->amount ?? 0));
if ($amount <= 0) {
continue;
}
$monthKey = date('Y-m', $timestamp);
$yearKey = (int)date('Y', $timestamp);
$availableMonths[$monthKey] = $monthKey;
$allAnimalIds[$animalId] = $animalId;
if (!isset($donationByAnimalMonth[$animalId])) {
$donationByAnimalMonth[$animalId] = [];
}
$donationByAnimalMonth[$animalId][$monthKey] = ($donationByAnimalMonth[$animalId][$monthKey] ?? 0.0) + $amount;
if (!isset($donationByAnimalYear[$animalId])) {
$donationByAnimalYear[$animalId] = [];
}
$donationByAnimalYear[$animalId][$yearKey] = ($donationByAnimalYear[$animalId][$yearKey] ?? 0.0) + $amount;
}
if (empty($availableMonths)) {
return $result;
}
rsort($availableMonths, SORT_STRING);
$result['months'] = array_values($availableMonths);
$requestedMonth = trim((string)Yii::$app->request->get('month', ''));
$selectedMonth = in_array($requestedMonth, $result['months'], true) ? $requestedMonth : $result['months'][0];
$result['selectedMonth'] = $selectedMonth;
$animalMetaById = $this->loadAnimalMeta(array_values($allAnimalIds));
if (!Yii::$app->user->isGuest) {
$currentUserId = (int)Yii::$app->user->id;
$userAnimalRows = [];
foreach ($transactions as $transaction) {
if ((int)($transaction->donor_user_id ?? 0) !== $currentUserId) {
continue;
}
$animalId = (int)($transaction->target_animal_id ?? 0);
if ($animalId <= 0 && (int)($transaction->goal_id ?? 0) > 0) {
$goal = $goalById[(int)$transaction->goal_id] ?? null;
if ($goal instanceof DonationGoal) {
$animalId = (int)($goal->target_animal_id ?? 0);
}
}
if ($animalId <= 0) {
continue;
}
$createdAt = trim((string)($transaction->created_at ?? ''));
$timestamp = $createdAt !== '' ? strtotime($createdAt) : false;
if ($timestamp === false) {
continue;
}
$amount = max(0.0, (float)($transaction->amount ?? 0));
if ($amount <= 0) {
continue;
}
$monthKey = date('Y-m', $timestamp);
$yearKey = (int)date('Y', $timestamp);
$goal = $goalByAnimalId[$animalId] ?? null;
$animalMeta = $animalMetaById[$animalId] ?? null;
if (!isset($userAnimalRows[$animalId])) {
$userAnimalRows[$animalId] = [
'animalId' => $animalId,
'animalName' => $animalMeta['name'] ?? ('Animal #' . $animalId),
'animalUrl' => $animalMeta['url'] ?? '',
'imageUrl' => $animalMeta['image'] ?? '',
'goalTarget' => $goal instanceof DonationGoal ? (float)$goal->target_amount : 0.0,
'goalCurrent' => $goal instanceof DonationGoal ? (float)$goal->current_amount : 0.0,
'goalCurrency' => $goal instanceof DonationGoal ? (string)$goal->currency : 'USD',
'total' => 0.0,
'monthly' => [],
'annual' => [],
];
}
$userAnimalRows[$animalId]['total'] += $amount;
$userAnimalRows[$animalId]['monthly'][$monthKey] = ($userAnimalRows[$animalId]['monthly'][$monthKey] ?? 0.0) + $amount;
$userAnimalRows[$animalId]['annual'][$yearKey] = ($userAnimalRows[$animalId]['annual'][$yearKey] ?? 0.0) + $amount;
}
uasort($userAnimalRows, static function (array $a, array $b): int {
return strnatcasecmp((string)$a['animalName'], (string)$b['animalName']);
});
$result['userRows'] = array_values($userAnimalRows);
$result['userMonthlyHeaders'] = $result['months'];
$result['userGrandTotal'] = array_reduce($result['userRows'], static function (float $carry, array $row): float {
return $carry + (float)($row['total'] ?? 0.0);
}, 0.0);
}
$adminAnimalIds = array_values(array_unique(array_merge(array_keys($goalByAnimalId), array_keys($donationByAnimalMonth))));
sort($adminAnimalIds, SORT_NUMERIC);
$adminRows = [];
$subtotalTarget = 0.0;
$subtotalDonated = 0.0;
foreach ($adminAnimalIds as $animalId) {
$goal = $goalByAnimalId[$animalId] ?? null;
$animalMeta = $animalMetaById[$animalId] ?? null;
$targetAmount = $goal instanceof DonationGoal ? (float)$goal->target_amount : 0.0;
$donatedAmount = (float)($donationByAnimalMonth[$animalId][$selectedMonth] ?? 0.0);
$percent = $targetAmount > 0 ? min(100.0, ($donatedAmount / $targetAmount) * 100.0) : 0.0;
$adminRows[] = [
'animalId' => $animalId,
'animalName' => $animalMeta['name'] ?? ('Animal #' . $animalId),
'animalUrl' => $animalMeta['url'] ?? '',
'target' => $targetAmount,
'donated' => $donatedAmount,
'percent' => $percent,
];
$subtotalTarget += $targetAmount;
$subtotalDonated += $donatedAmount;
}
usort($adminRows, static function (array $a, array $b): int {
return strnatcasecmp((string)$a['animalName'], (string)$b['animalName']);
});
$subtotalPercent = $subtotalTarget > 0 ? min(100.0, ($subtotalDonated / $subtotalTarget) * 100.0) : 0.0;
$result['adminRows'] = $adminRows;
$result['adminTotals'] = [
'target' => $subtotalTarget,
'donated' => $subtotalDonated,
'percent' => $subtotalPercent,
];
$currentYear = (int)date('Y');
$previousYear = $currentYear - 1;
$ytdDonated = 0.0;
$previousYearDonated = 0.0;
foreach ($donationByAnimalYear as $animalId => $yearRows) {
$ytdDonated += (float)($yearRows[$currentYear] ?? 0.0);
$previousYearDonated += (float)($yearRows[$previousYear] ?? 0.0);
}
$totalActiveTarget = 0.0;
foreach ($goalByAnimalId as $goal) {
if (!$goal instanceof DonationGoal) {
continue;
}
$totalActiveTarget += max(0.0, (float)$goal->target_amount);
}
$result['ytd'] = [
'year' => $currentYear,
'donated' => $ytdDonated,
'target' => $totalActiveTarget,
'percent' => $totalActiveTarget > 0 ? min(100.0, ($ytdDonated / $totalActiveTarget) * 100.0) : 0.0,
];
if ($previousYearDonated > 0.0) {
$result['previousYear'] = [
'year' => $previousYear,
'donated' => $previousYearDonated,
'target' => $totalActiveTarget,
'percent' => $totalActiveTarget > 0 ? min(100.0, ($previousYearDonated / $totalActiveTarget) * 100.0) : 0.0,
];
}
return $result;
}
private function canManageDonationDashboard(): bool
{
if (!($this->contentContainer instanceof Space) || Yii::$app->user->isGuest) {
return false;
}
$group = $this->contentContainer->getUserGroup(Yii::$app->user->getIdentity());
return in_array($group, [
Space::USERGROUP_OWNER,
Space::USERGROUP_ADMIN,
Space::USERGROUP_MODERATOR,
Space::USERGROUP_MEMBER,
], true);
}
private function loadAnimalMeta(array $animalIds): array
{
$result = [];
$animalIds = array_values(array_unique(array_filter(array_map('intval', $animalIds))));
if (empty($animalIds)) {
return $result;
}
$animalClass = 'humhub\\modules\\animal_management\\models\\Animal';
$galleryClass = 'humhub\\modules\\animal_management\\models\\AnimalGalleryItem';
if (!class_exists($animalClass) || Yii::$app->db->schema->getTableSchema($animalClass::tableName(), true) === null) {
return $result;
}
$animals = $animalClass::find()->where([
'id' => $animalIds,
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
])->all();
foreach ($animals as $animal) {
$animalId = (int)($animal->id ?? 0);
if ($animalId <= 0) {
continue;
}
$imageUrl = '';
if (class_exists($galleryClass) && Yii::$app->db->schema->getTableSchema($galleryClass::tableName(), true) !== null) {
$galleryItem = $galleryClass::find()->where(['animal_id' => $animalId])->orderBy(['id' => SORT_DESC])->one();
if ($galleryItem !== null && method_exists($galleryItem, 'getImageUrl')) {
$imageUrl = trim((string)$galleryItem->getImageUrl());
}
}
$displayName = method_exists($animal, 'getDisplayName')
? (string)$animal->getDisplayName()
: trim((string)($animal->name ?? ''));
if ($displayName === '') {
$displayName = 'Animal #' . $animalId;
}
$result[$animalId] = [
'name' => $displayName,
'url' => $this->contentContainer->createUrl('/animal_management/animals/view', ['id' => $animalId]),
'image' => $imageUrl,
];
}
return $result;
}
public function actionStripeWebhook()
{
Yii::$app->response->format = Response::FORMAT_JSON;
if (!$this->isSchemaReady()) {
Yii::$app->response->statusCode = 400;
return ['ok' => false, 'message' => 'Donations schema not ready'];
}
$providerConfig = DonationProviderConfig::findOne([
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
]);
if (!$providerConfig instanceof DonationProviderConfig || (int)$providerConfig->stripe_enabled !== 1) {
Yii::$app->response->statusCode = 404;
return ['ok' => false, 'message' => 'Stripe is not enabled for this space'];
}
$payload = (string)Yii::$app->request->getRawBody();
$signature = (string)Yii::$app->request->headers->get('Stripe-Signature', '');
$service = new StripeWebhookService();
$result = $service->process($payload, $signature, $providerConfig);
Yii::$app->response->statusCode = (int)($result['status'] ?? 200);
return [
'ok' => (bool)($result['ok'] ?? false),
'message' => (string)($result['message'] ?? ''),
];
}
public function actionPaypalWebhook()
{
Yii::$app->response->format = Response::FORMAT_JSON;
if (!$this->isSchemaReady()) {
Yii::$app->response->statusCode = 400;
return ['ok' => false, 'message' => 'Donations schema not ready'];
}
$providerConfig = DonationProviderConfig::findOne([
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
]);
if (!$providerConfig instanceof DonationProviderConfig || (int)$providerConfig->paypal_enabled !== 1) {
Yii::$app->response->statusCode = 404;
return ['ok' => false, 'message' => 'PayPal is not enabled for this space'];
}
$payload = (string)Yii::$app->request->getRawBody();
$headers = Yii::$app->request->headers->toArray();
$service = new PayPalWebhookService();
$result = $service->process($payload, $headers, $providerConfig);
Yii::$app->response->statusCode = (int)($result['status'] ?? 200);
return [
'ok' => (bool)($result['ok'] ?? false),
'message' => (string)($result['message'] ?? ''),
];
}
}