617 lines
25 KiB
PHP
617 lines
25 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\DonationSubscription;
|
|
use humhub\modules\donations\models\DonationTransaction;
|
|
use humhub\modules\donations\models\DonationWebhookEvent;
|
|
use humhub\modules\donations\models\forms\DonationGoalForm;
|
|
use humhub\modules\donations\models\forms\ProviderSettingsForm;
|
|
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\ProviderCredentialResolver;
|
|
use humhub\modules\donations\services\providers\StripeWebhookService;
|
|
use humhub\modules\donations\services\ModuleSetupService;
|
|
use humhub\modules\rescue_foundation\widgets\RescueSettingsMenu;
|
|
use humhub\modules\space\models\Space;
|
|
use Yii;
|
|
use yii\filters\VerbFilter;
|
|
use yii\web\UploadedFile;
|
|
use yii\web\BadRequestHttpException;
|
|
use yii\web\NotFoundHttpException;
|
|
|
|
class SettingsController extends ContentContainerController
|
|
{
|
|
public function behaviors()
|
|
{
|
|
$behaviors = parent::behaviors();
|
|
$behaviors['verbs'] = [
|
|
'class' => VerbFilter::class,
|
|
'actions' => [
|
|
'setup' => ['post'],
|
|
'delete-goal' => ['post'],
|
|
'simulate-stripe-webhook' => ['post'],
|
|
'simulate-paypal-webhook' => ['post'],
|
|
'reconcile-pending' => ['post'],
|
|
'create-animal-goal-inline' => ['post'],
|
|
],
|
|
];
|
|
|
|
return $behaviors;
|
|
}
|
|
|
|
protected function getAccessRules()
|
|
{
|
|
return [[
|
|
ContentContainerControllerAccess::RULE_USER_GROUP_ONLY => [
|
|
Space::USERGROUP_OWNER,
|
|
Space::USERGROUP_ADMIN,
|
|
],
|
|
]];
|
|
}
|
|
|
|
public function actionIndex($goalId = null)
|
|
{
|
|
$activeTab = $this->getRequestedTab();
|
|
$subNav = null;
|
|
if (class_exists(RescueSettingsMenu::class)) {
|
|
$subNav = RescueSettingsMenu::widget(['space' => $this->contentContainer]);
|
|
}
|
|
|
|
$providerForm = new ProviderSettingsForm([
|
|
'contentContainer' => $this->contentContainer,
|
|
]);
|
|
|
|
$schemaReady = $this->isSchemaReady();
|
|
if ($schemaReady) {
|
|
$providerForm->loadValues();
|
|
}
|
|
|
|
$goalForm = new DonationGoalForm([
|
|
'contentContainer' => $this->contentContainer,
|
|
]);
|
|
|
|
if ($schemaReady && $goalId !== null) {
|
|
$goal = DonationGoal::findOne([
|
|
'id' => (int)$goalId,
|
|
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
|
|
]);
|
|
|
|
if ($goal instanceof DonationGoal) {
|
|
$goalForm->loadFromGoal($goal);
|
|
$activeTab = 'goals';
|
|
}
|
|
} elseif ($schemaReady && Yii::$app->request->post('DonationGoalForm') === null) {
|
|
$prefillGoalType = trim((string)Yii::$app->request->get('goalType', ''));
|
|
$prefillAnimalId = (int)Yii::$app->request->get('targetAnimalId', 0);
|
|
|
|
if ($prefillGoalType === DonationGoal::TYPE_ANIMAL) {
|
|
$goalForm->goal_type = DonationGoal::TYPE_ANIMAL;
|
|
$activeTab = 'goals';
|
|
$animalOptions = $goalForm->getAnimalOptions();
|
|
|
|
if ($prefillAnimalId > 0 && isset($animalOptions[$prefillAnimalId])) {
|
|
$goalForm->target_animal_id = $prefillAnimalId;
|
|
|
|
if (trim((string)$goalForm->title) === '') {
|
|
$goalForm->title = Yii::t('DonationsModule.base', '{animalName} Care Fund', [
|
|
'animalName' => (string)$animalOptions[$prefillAnimalId],
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Yii::$app->request->post('ProviderSettingsForm') !== null) {
|
|
$activeTab = $this->sanitizeTab((string)Yii::$app->request->post('active_tab', 'payment-providers'));
|
|
|
|
if (!$schemaReady) {
|
|
$this->view->error(Yii::t('DonationsModule.base', 'Please run Donations setup first.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'advanced',
|
|
]));
|
|
}
|
|
|
|
if ($providerForm->load(Yii::$app->request->post()) && $providerForm->save()) {
|
|
$this->view->success(Yii::t('DonationsModule.base', 'Provider settings saved.'));
|
|
$redirectTab = trim((string)Yii::$app->request->post('active_tab', 'payment-providers'));
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => $this->sanitizeTab($redirectTab),
|
|
]));
|
|
}
|
|
}
|
|
|
|
if (Yii::$app->request->post('DonationGoalForm') !== null) {
|
|
if (!$schemaReady) {
|
|
$this->view->error(Yii::t('DonationsModule.base', 'Please run Donations setup first.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'advanced',
|
|
]));
|
|
}
|
|
|
|
if ($goalForm->load(Yii::$app->request->post())) {
|
|
$goalForm->imageFile = UploadedFile::getInstance($goalForm, 'imageFile');
|
|
}
|
|
|
|
if ($goalForm->save()) {
|
|
$this->view->success(Yii::t('DonationsModule.base', 'Donation goal saved.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'goals',
|
|
]));
|
|
}
|
|
|
|
$activeTab = 'goals';
|
|
}
|
|
|
|
$goals = [];
|
|
$transactions = [];
|
|
$subscriptions = [];
|
|
$webhookEvents = [];
|
|
$animalGalleryImageMap = [];
|
|
if ($schemaReady) {
|
|
$goals = DonationGoal::find()
|
|
->where(['contentcontainer_id' => $this->contentContainer->contentcontainer_id])
|
|
->orderBy(['is_active' => SORT_DESC, 'id' => SORT_DESC])
|
|
->all();
|
|
|
|
$transactions = DonationTransaction::find()
|
|
->where(['contentcontainer_id' => $this->contentContainer->contentcontainer_id])
|
|
->orderBy(['id' => SORT_DESC])
|
|
->limit(150)
|
|
->all();
|
|
|
|
$subscriptions = DonationSubscription::find()
|
|
->where(['contentcontainer_id' => $this->contentContainer->contentcontainer_id])
|
|
->orderBy(['id' => SORT_DESC])
|
|
->limit(150)
|
|
->all();
|
|
|
|
$webhookEvents = DonationWebhookEvent::find()
|
|
->orderBy(['id' => SORT_DESC])
|
|
->limit(150)
|
|
->all();
|
|
|
|
$animalGalleryImageMap = $this->resolveAnimalGalleryImageMap($goalForm, $goalForm->getAnimalOptions());
|
|
}
|
|
|
|
return $this->render('index', [
|
|
'subNav' => $subNav,
|
|
'providerForm' => $providerForm,
|
|
'goalForm' => $goalForm,
|
|
'goals' => $goals,
|
|
'animalOptions' => $schemaReady ? $goalForm->getAnimalOptions() : [],
|
|
'animalGalleryImageMap' => $animalGalleryImageMap,
|
|
'transactions' => $transactions,
|
|
'subscriptions' => $subscriptions,
|
|
'webhookEvents' => $webhookEvents,
|
|
'activeTab' => $activeTab,
|
|
'schemaReady' => $schemaReady,
|
|
]);
|
|
}
|
|
|
|
public function actionSetup()
|
|
{
|
|
if (!Yii::$app->request->isPost) {
|
|
throw new BadRequestHttpException('Invalid request method.');
|
|
}
|
|
|
|
if (!$this->contentContainer instanceof Space) {
|
|
$this->view->error(Yii::t('DonationsModule.base', 'Setup can only be run inside a space.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'advanced',
|
|
]));
|
|
}
|
|
|
|
try {
|
|
$result = ModuleSetupService::runForSpace($this->contentContainer);
|
|
$appliedCount = count($result['applied'] ?? []);
|
|
|
|
if ($appliedCount > 0) {
|
|
$this->view->success(Yii::t('DonationsModule.base', 'Setup completed. Applied {count} migration(s).', [
|
|
'count' => $appliedCount,
|
|
]));
|
|
} else {
|
|
$this->view->success(Yii::t('DonationsModule.base', 'Setup completed. No pending migrations were found.'));
|
|
}
|
|
} catch (\Throwable $e) {
|
|
Yii::error($e, 'donations.setup');
|
|
$this->view->error(Yii::t('DonationsModule.base', 'Setup failed. Please check logs and try again.'));
|
|
}
|
|
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'advanced',
|
|
]));
|
|
}
|
|
|
|
public function actionDeleteGoal($id)
|
|
{
|
|
if (!$this->isSchemaReady()) {
|
|
$this->view->error(Yii::t('DonationsModule.base', 'Please run Donations setup first.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'advanced',
|
|
]));
|
|
}
|
|
|
|
$goal = DonationGoal::findOne([
|
|
'id' => (int)$id,
|
|
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
|
|
]);
|
|
|
|
if (!$goal instanceof DonationGoal) {
|
|
throw new NotFoundHttpException('Donation goal not found.');
|
|
}
|
|
|
|
$goal->delete();
|
|
$this->view->success(Yii::t('DonationsModule.base', 'Donation goal deleted.'));
|
|
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'goals',
|
|
]));
|
|
}
|
|
|
|
public function actionCreateAnimalGoalInline($animalId)
|
|
{
|
|
if (!$this->isSchemaReady()) {
|
|
$this->view->error(Yii::t('DonationsModule.base', 'Please run Donations setup first.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'advanced',
|
|
]));
|
|
}
|
|
|
|
$goalForm = new DonationGoalForm([
|
|
'contentContainer' => $this->contentContainer,
|
|
'goal_type' => DonationGoal::TYPE_ANIMAL,
|
|
'target_animal_id' => (int)$animalId,
|
|
]);
|
|
|
|
$postGoalData = Yii::$app->request->post('DonationGoalForm', []);
|
|
$postedGoalId = (int)($postGoalData['id'] ?? 0);
|
|
if ($postedGoalId > 0) {
|
|
$existingGoal = DonationGoal::findOne([
|
|
'id' => $postedGoalId,
|
|
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
|
|
'goal_type' => DonationGoal::TYPE_ANIMAL,
|
|
'target_animal_id' => (int)$animalId,
|
|
]);
|
|
|
|
if ($existingGoal instanceof DonationGoal) {
|
|
$goalForm->loadFromGoal($existingGoal);
|
|
}
|
|
}
|
|
|
|
if ($goalForm->load(Yii::$app->request->post())) {
|
|
$goalForm->goal_type = DonationGoal::TYPE_ANIMAL;
|
|
$goalForm->target_animal_id = (int)$animalId;
|
|
$goalForm->imageFile = UploadedFile::getInstance($goalForm, 'imageFile');
|
|
|
|
if ($goalForm->save()) {
|
|
$this->view->success(Yii::t('DonationsModule.base', 'Donation goal saved.'));
|
|
} else {
|
|
$errors = $goalForm->getFirstErrors();
|
|
$message = !empty($errors)
|
|
? implode(' ', array_values($errors))
|
|
: Yii::t('DonationsModule.base', 'Unable to save donation goal.');
|
|
$this->view->error($message);
|
|
}
|
|
} else {
|
|
$this->view->error(Yii::t('DonationsModule.base', 'Invalid donation goal request.'));
|
|
}
|
|
|
|
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/index'));
|
|
}
|
|
|
|
public function actionHistory()
|
|
{
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'donation-history',
|
|
]));
|
|
}
|
|
|
|
public function actionSimulateStripeWebhook()
|
|
{
|
|
if (!$this->isSchemaReady()) {
|
|
$this->view->error(Yii::t('DonationsModule.base', 'Please run Donations setup first.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'advanced',
|
|
]));
|
|
}
|
|
|
|
$config = DonationProviderConfig::findOne(['contentcontainer_id' => $this->contentContainer->contentcontainer_id]);
|
|
if (!$config instanceof DonationProviderConfig || (int)$config->stripe_enabled !== 1) {
|
|
$this->view->error(Yii::t('DonationsModule.base', 'Stripe is not enabled for this space.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'advanced',
|
|
]));
|
|
}
|
|
|
|
$secret = (new ProviderCredentialResolver($this->contentContainer))->resolveStripeWebhookSecret($config);
|
|
$secret = trim((string)$secret);
|
|
if ($secret === '') {
|
|
$this->view->error(Yii::t('DonationsModule.base', 'Stripe webhook secret is required to run simulation.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'advanced',
|
|
]));
|
|
}
|
|
|
|
$transaction = $this->resolveSimulationTransaction('stripe');
|
|
if (!$transaction instanceof DonationTransaction) {
|
|
$this->view->error(Yii::t('DonationsModule.base', 'No Stripe transaction found to simulate. Create a Stripe donation intent first.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'advanced',
|
|
]));
|
|
}
|
|
|
|
$sessionId = $transaction->provider_checkout_id ?: ('cs_test_' . Yii::$app->security->generateRandomString(16));
|
|
$paymentIntentId = $transaction->provider_payment_id ?: ('pi_test_' . Yii::$app->security->generateRandomString(16));
|
|
|
|
$event = [
|
|
'id' => 'evt_test_' . Yii::$app->security->generateRandomString(16),
|
|
'type' => 'checkout.session.completed',
|
|
'data' => [
|
|
'object' => [
|
|
'id' => $sessionId,
|
|
'payment_intent' => $paymentIntentId,
|
|
'subscription' => $transaction->mode === 'recurring'
|
|
? ($transaction->provider_subscription_id ?: ('sub_test_' . Yii::$app->security->generateRandomString(14)))
|
|
: null,
|
|
'customer' => $transaction->provider_customer_id ?: ('cus_test_' . Yii::$app->security->generateRandomString(12)),
|
|
'metadata' => [
|
|
'transaction_id' => (string)$transaction->id,
|
|
'goal_id' => (string)$transaction->goal_id,
|
|
'contentcontainer_id' => (string)$transaction->contentcontainer_id,
|
|
],
|
|
],
|
|
],
|
|
];
|
|
|
|
$payload = json_encode($event, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
$timestamp = (string)time();
|
|
$signature = hash_hmac('sha256', $timestamp . '.' . $payload, $secret);
|
|
$signatureHeader = 't=' . $timestamp . ',v1=' . $signature;
|
|
|
|
$service = new StripeWebhookService();
|
|
$result = $service->process($payload, $signatureHeader, $config);
|
|
|
|
if (!($result['ok'] ?? false)) {
|
|
$this->view->error(Yii::t('DonationsModule.base', 'Stripe simulation failed: {message}', [
|
|
'message' => (string)($result['message'] ?? 'Unknown error'),
|
|
]));
|
|
} else {
|
|
$this->view->success(Yii::t('DonationsModule.base', 'Stripe webhook simulation processed successfully.'));
|
|
}
|
|
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'advanced',
|
|
]));
|
|
}
|
|
|
|
public function actionSimulatePaypalWebhook()
|
|
{
|
|
if (!$this->isSchemaReady()) {
|
|
$this->view->error(Yii::t('DonationsModule.base', 'Please run Donations setup first.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'advanced',
|
|
]));
|
|
}
|
|
|
|
$config = DonationProviderConfig::findOne(['contentcontainer_id' => $this->contentContainer->contentcontainer_id]);
|
|
if (!$config instanceof DonationProviderConfig || (int)$config->paypal_enabled !== 1) {
|
|
$this->view->error(Yii::t('DonationsModule.base', 'PayPal is not enabled for this space.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'advanced',
|
|
]));
|
|
}
|
|
|
|
$transaction = $this->resolveSimulationTransaction('paypal');
|
|
if (!$transaction instanceof DonationTransaction) {
|
|
$this->view->error(Yii::t('DonationsModule.base', 'No PayPal transaction found to simulate. Create a PayPal donation intent first.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'advanced',
|
|
]));
|
|
}
|
|
|
|
$orderId = $transaction->provider_checkout_id ?: ('ORDER-' . strtoupper(Yii::$app->security->generateRandomString(12)));
|
|
$captureId = $transaction->provider_payment_id ?: ('CAPTURE-' . strtoupper(Yii::$app->security->generateRandomString(12)));
|
|
|
|
$event = [
|
|
'id' => 'WH-TEST-' . strtoupper(Yii::$app->security->generateRandomString(12)),
|
|
'event_type' => 'PAYMENT.CAPTURE.COMPLETED',
|
|
'resource' => [
|
|
'id' => $captureId,
|
|
'custom_id' => (string)$transaction->id,
|
|
'supplementary_data' => [
|
|
'related_ids' => [
|
|
'order_id' => $orderId,
|
|
],
|
|
],
|
|
],
|
|
];
|
|
|
|
$service = new PayPalWebhookService();
|
|
$result = $service->processTestEvent($event);
|
|
|
|
if (!($result['ok'] ?? false)) {
|
|
$this->view->error(Yii::t('DonationsModule.base', 'PayPal simulation failed: {message}', [
|
|
'message' => (string)($result['message'] ?? 'Unknown error'),
|
|
]));
|
|
} else {
|
|
$this->view->success(Yii::t('DonationsModule.base', 'PayPal webhook simulation processed successfully.'));
|
|
}
|
|
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'advanced',
|
|
]));
|
|
}
|
|
|
|
public function actionReconcilePending()
|
|
{
|
|
if (!$this->isSchemaReady()) {
|
|
$this->view->error(Yii::t('DonationsModule.base', 'Please run Donations setup first.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'advanced',
|
|
]));
|
|
}
|
|
|
|
$config = DonationProviderConfig::findOne(['contentcontainer_id' => $this->contentContainer->contentcontainer_id]);
|
|
if (!$config instanceof DonationProviderConfig) {
|
|
$this->view->error(Yii::t('DonationsModule.base', 'Provider settings are required before reconciliation can run.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'advanced',
|
|
]));
|
|
}
|
|
|
|
$pending = DonationTransaction::find()
|
|
->where([
|
|
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
|
|
'status' => DonationTransaction::STATUS_PENDING,
|
|
])
|
|
->orderBy(['id' => SORT_ASC])
|
|
->limit(150)
|
|
->all();
|
|
|
|
if (empty($pending)) {
|
|
$this->view->info(Yii::t('DonationsModule.base', 'No pending transactions found for reconciliation.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'advanced',
|
|
]));
|
|
}
|
|
|
|
$settlement = new DonationSettlementService();
|
|
$gateway = new PaymentGatewayService();
|
|
$reconciled = 0;
|
|
$skipped = 0;
|
|
$failed = 0;
|
|
|
|
foreach ($pending as $transaction) {
|
|
if ($transaction->provider === 'paypal' && $transaction->mode === 'one_time' && (int)$config->paypal_enabled === 1) {
|
|
$captureResult = $gateway->capturePayPalOrder($transaction, $config);
|
|
|
|
if (!($captureResult['success'] ?? false)) {
|
|
$failed++;
|
|
$metadata = json_decode((string)$transaction->metadata_json, true);
|
|
if (!is_array($metadata)) {
|
|
$metadata = [];
|
|
}
|
|
$metadata['paypal_reconcile_capture'] = [
|
|
'success' => false,
|
|
'checked_at' => date('c'),
|
|
'status' => (int)($captureResult['status'] ?? 0),
|
|
'error' => (string)($captureResult['error'] ?? 'Capture failed.'),
|
|
];
|
|
$transaction->metadata_json = json_encode($metadata, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
$transaction->save(false);
|
|
continue;
|
|
}
|
|
|
|
$transaction->provider_payment_id = (string)($captureResult['payment_id'] ?? $transaction->provider_payment_id);
|
|
$settlement->markSucceededAndApply($transaction, [
|
|
'paypal_reconciled_at' => date('c'),
|
|
'paypal_order_already_captured' => !empty($captureResult['already_captured']) ? 1 : 0,
|
|
]);
|
|
$reconciled++;
|
|
continue;
|
|
}
|
|
|
|
$skipped++;
|
|
}
|
|
|
|
if ($reconciled > 0) {
|
|
$this->view->success(Yii::t('DonationsModule.base', 'Reconciliation finished. Reconciled {reconciled} transaction(s), skipped {skipped}, failed {failed}.', [
|
|
'reconciled' => $reconciled,
|
|
'skipped' => $skipped,
|
|
'failed' => $failed,
|
|
]));
|
|
} else {
|
|
$this->view->info(Yii::t('DonationsModule.base', 'Reconciliation finished. No transactions were reconciled. Skipped {skipped}, failed {failed}.', [
|
|
'skipped' => $skipped,
|
|
'failed' => $failed,
|
|
]));
|
|
}
|
|
|
|
return $this->redirect($this->contentContainer->createUrl('/donations/settings', [
|
|
'tab' => 'advanced',
|
|
]));
|
|
}
|
|
|
|
private function resolveSimulationTransaction(string $provider): ?DonationTransaction
|
|
{
|
|
$transactionId = (int)Yii::$app->request->post('transaction_id', 0);
|
|
if ($transactionId > 0) {
|
|
$tx = DonationTransaction::findOne([
|
|
'id' => $transactionId,
|
|
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
|
|
'provider' => $provider,
|
|
]);
|
|
|
|
if ($tx instanceof DonationTransaction) {
|
|
return $tx;
|
|
}
|
|
}
|
|
|
|
$tx = DonationTransaction::find()
|
|
->where([
|
|
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
|
|
'provider' => $provider,
|
|
'status' => DonationTransaction::STATUS_PENDING,
|
|
])
|
|
->orderBy(['id' => SORT_DESC])
|
|
->one();
|
|
|
|
if ($tx instanceof DonationTransaction) {
|
|
return $tx;
|
|
}
|
|
|
|
return DonationTransaction::find()
|
|
->where([
|
|
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
|
|
'provider' => $provider,
|
|
])
|
|
->orderBy(['id' => SORT_DESC])
|
|
->one();
|
|
}
|
|
|
|
private function getRequestedTab(): string
|
|
{
|
|
return $this->sanitizeTab((string)Yii::$app->request->get('tab', 'general'));
|
|
}
|
|
|
|
private function sanitizeTab(string $tab): string
|
|
{
|
|
$allowed = [
|
|
'general',
|
|
'goals',
|
|
'payment-providers',
|
|
'donation-history',
|
|
'advanced',
|
|
];
|
|
|
|
return in_array($tab, $allowed, true) ? $tab : 'general';
|
|
}
|
|
|
|
private function resolveAnimalGalleryImageMap(DonationGoalForm $goalForm, array $animalOptions): array
|
|
{
|
|
$map = [];
|
|
foreach (array_keys($animalOptions) as $animalId) {
|
|
$options = $goalForm->getGalleryImageOptionsForAnimal((int)$animalId);
|
|
if (empty($options)) {
|
|
continue;
|
|
}
|
|
|
|
$map[(int)$animalId] = array_values(array_keys($options));
|
|
}
|
|
|
|
return $map;
|
|
}
|
|
|
|
private function isSchemaReady(): bool
|
|
{
|
|
return Yii::$app->db->schema->getTableSchema(DonationGoal::tableName(), true) !== null
|
|
&& Yii::$app->db->schema->getTableSchema(DonationProviderConfig::tableName(), true) !== null;
|
|
}
|
|
}
|