diff --git a/config/access.php b/config/access.php index 54598d711..3b8ab5d12 100644 --- a/config/access.php +++ b/config/access.php @@ -15,7 +15,8 @@ 'update' => AccountLevel::ANYONE, 'complete' => AccountLevel::ANYONE, 'history' => AccountLevel::NORMAL, - 'trusted' => AccountLevel::NORMAL + 'trusted' => AccountLevel::NORMAL, + 'admin' => AccountLevel::ADMIN ), 'purchase' => array( 'index' => AccountLevel::ANYONE, @@ -134,7 +135,7 @@ 'changemail' => AccountLevel::ADMIN, 'ban' => AccountLevel::ADMIN, 'ipban' => AccountLevel::ADMIN, - 'txnview' => AccountLevel::ADMIN + 'txnview' => AccountLevel::ADMIN ), 'ipban' => array( 'index' => AccountLevel::ADMIN, @@ -198,7 +199,7 @@ 'vending' => array( 'index' => AccountLevel::ANYONE, 'viewshop' => AccountLevel::ANYONE, - ), + ), 'webcommands' => array( 'index' => AccountLevel::ADMIN, ), @@ -250,7 +251,7 @@ 'SeeAccountID' => AccountLevel::LOWGM, // Minimum group level required to see Account ID on account view and character view pages. 'SeeUnknownItems' => AccountLevel::LOWGM, // Minimum group level required to see unidentified items as identified. 'AvoidSexChangeCost' => AccountLevel::LOWGM, // Avoid paying cost (if any) for sex changes. - + 'EditHigherPower' => AccountLevel::NOONE, 'BanHigherPower' => AccountLevel::NOONE ) diff --git a/config/application.php b/config/application.php index b3f730854..018ca25b5 100644 --- a/config/application.php +++ b/config/application.php @@ -97,6 +97,17 @@ 'MoneyThousandsSymbol' => ',', // (Visual) Thousandths place separator (a period in European currencies). 'MoneyDecimalSymbol' => '.', // (Visual) Decimal separator (a comma in European currencies). 'AcceptDonations' => true, // Whether or not to accept donations. + + // Payment Gateway Options + 'PaymentGateway' => ['stripe', 'paypal'], // Payment Gateway to use. Options: ['stripe', 'paypal] + + // Stripe Options + 'StripeButtonId' => '...', // Stripe Button ID + 'StripeSecretKey' => '...', // Stripe Secret Key + 'StripePublishableKey' => '...', // Stripe Publishable Key + 'StripeWebhookSecret' => '...', // Stripe Webhook Secret + + // PayPal Options 'PayPalIpnUrl' => 'www.paypal.com', // The ipnpb.paypal.com and ipnpb.sandbox.paypal.com endpoints only accept HTTPS connections. If you currently use www.paypal.com, you should move to ipnpb.paypal.com when you update your code to use HTTPS. 'PayPalBusinessEmail' => 'admin@localhost', // Enter the e-mail under which you have registered your business account. 'PayPalReceiverEmails' => array( // These are the receiver e-mail addresses who are allowed to receive payment. @@ -542,6 +553,8 @@ 'ServiceDeskSettingsTable' => 'cp_servicedesksettings', 'WebCommandsTable' => 'cp_commands', 'ItemDescTable' => 'cp_itemdesc', + 'StripeTransactionTable' => 'cp_stripetransactions', + 'StripeTransactionLogTable' => 'cp_stripetransactionslog', ) ); ?> diff --git a/data/schemas/logindb/cp_stripetransactions.20240323020801.sql b/data/schemas/logindb/cp_stripetransactions.20240323020801.sql new file mode 100644 index 000000000..67704316b --- /dev/null +++ b/data/schemas/logindb/cp_stripetransactions.20240323020801.sql @@ -0,0 +1,18 @@ +CREATE TABLE IF NOT EXISTS `cp_stripetransactions` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `event_reference_id` varchar(255) DEFAULT NULL, + `account_id` int(11) unsigned DEFAULT 0, + `email` varchar(60) DEFAULT NULL, + `server_name` varchar(255) DEFAULT NULL, + `credits` int(11) DEFAULT NULL, + `amount` int(11) DEFAULT NULL, + `settleAmount` int(11) DEFAULT NULL, + `settleCurrency` varchar(3) DEFAULT NULL, + `status` varchar(10) DEFAULT NULL, + `payment_status` varchar(10) DEFAULT NULL, + `json_payload` longtext DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT current_timestamp(), + PRIMARY KEY (`id`), + KEY `event_id` (`event_reference_id`), + KEY `account_id` (`account_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='All Stripe transactions logs.' AUTO_INCREMENT=1; diff --git a/data/schemas/logindb/cp_stripetransactionslogs.20240323020702.sql b/data/schemas/logindb/cp_stripetransactionslogs.20240323020702.sql new file mode 100644 index 000000000..67704316b --- /dev/null +++ b/data/schemas/logindb/cp_stripetransactionslogs.20240323020702.sql @@ -0,0 +1,18 @@ +CREATE TABLE IF NOT EXISTS `cp_stripetransactions` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `event_reference_id` varchar(255) DEFAULT NULL, + `account_id` int(11) unsigned DEFAULT 0, + `email` varchar(60) DEFAULT NULL, + `server_name` varchar(255) DEFAULT NULL, + `credits` int(11) DEFAULT NULL, + `amount` int(11) DEFAULT NULL, + `settleAmount` int(11) DEFAULT NULL, + `settleCurrency` varchar(3) DEFAULT NULL, + `status` varchar(10) DEFAULT NULL, + `payment_status` varchar(10) DEFAULT NULL, + `json_payload` longtext DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT current_timestamp(), + PRIMARY KEY (`id`), + KEY `event_id` (`event_reference_id`), + KEY `account_id` (`account_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='All Stripe transactions logs.' AUTO_INCREMENT=1; diff --git a/data/stripe/button.php b/data/stripe/button.php new file mode 100644 index 000000000..955d9b41f --- /dev/null +++ b/data/stripe/button.php @@ -0,0 +1,22 @@ + $session->loginAthenaGroup->serverName, 'account_id' => $session->account->account_id); +$customDataEscaped = htmlspecialchars(base64_encode(serialize($customDataArray))); +?> + + + +
diff --git a/data/stripe/index.html b/data/stripe/index.html new file mode 100644 index 000000000..e69de29bb diff --git a/lib/Flux/PaymentNotifyRequest.php b/lib/Flux/PaypalNotifyRequest.php similarity index 100% rename from lib/Flux/PaymentNotifyRequest.php rename to lib/Flux/PaypalNotifyRequest.php diff --git a/lib/Flux/StripeNotifyRequest.php b/lib/Flux/StripeNotifyRequest.php new file mode 100755 index 000000000..5e412a937 --- /dev/null +++ b/lib/Flux/StripeNotifyRequest.php @@ -0,0 +1,492 @@ +server = $server; + $this->logStripe = new Flux_LogFile(FLUX_DATA_DIR.'/logs/stripe.log'); + $this->creditsTable = Flux::config('FluxTables.CreditsTable'); + $this->txnTable = Flux::config('FluxTables.StripeTransactions'); + $this->txnLogTable = Flux::config('FluxTables.StripeTransactionLogTable'); + $this->myCurrencyCode = strtoupper(Flux::config('DonationCurrency')); + $this->webhookSecret = Flux::config('StripeWebhookSecret'); + $this->sigHeader = $_SERVER['HTTP_STRIPE_SIGNATURE']; + $this->payload = @file_get_contents("php://input"); + } + + /** + * Log to Stripe log file. Works like printf(). + * + * @param string $format + * @param mixed ... + * @return string + * @access protected + */ + protected function logStripe() + { + $args = func_get_args(); + $func = array($this->logStripe, 'puts'); + return call_user_func_array($func, $args); + } + + /** + * Process transaction. + * + * @access public + */ + public function process() + { + if (!$this->verifyHeader($this->payload, $this->sigHeader, $this->webhookSecret, $this->DEFAULT_TOLERANCE)) { + http_response_code(400); + return; + } + + $this->logStripe('Webhook received!'); + + $event = json_decode($this->payload, true); + $eventObj = $event['data']['object']; + + switch ($event['type']) { + case 'payment_intent.created': + $this->createTransactionLog($eventObj, 'payment_intent.created'); + $this->logStripe('PaymentIntent was created!'); + break; + + case 'charge.succeeded': + $this->createTransactionLog($eventObj, 'charge.succeeded'); + $this->logStripe('PaymentMethod was attached to a Customer!'); + break; + + case 'payment_intent.succeeded': + $this->createTransactionLog($eventObj, 'payment_intent.succeeded'); + $this->logStripe('PaymentIntent was successful!'); + break; + + case 'payment_intent.payment_failed': + $this->createTransactionLog($eventObj, 'payment_intent.payment_failed'); + $this->logStripe('PaymentIntent failed!'); + break; + + case 'checkout.session.completed': + $this->logStripe('Checkout session was completed!'); + $this->createTransactionLog($eventObj, 'checkout.session.completed'); + $this->createTransaction($eventObj); + break; + default: + http_response_code(400); + return; + } + + http_response_code(200); + return; + } + + /** + * Verifies the signature header sent by Stripe. + * + * @param string $payload the payload sent by Stripe + * @param string $header the contents of the signature header sent by Stripe + * @param string $secret secret used to generate the signature + * @param int $tolerance maximum difference allowed between the header's timestamp and the current time + * + * + * @return bool + */ + public function verifyHeader($payload, $header, $secret, $tolerance = null) + { + // Extract timestamp and signatures from header + $timestamp = $this->getTimestamp($header); + $signatures = $this->getSignatures($header, $this->EXPECTED_SCHEME); + if (-1 === $timestamp) { + $this->logStripe('Unable to extract timestamp and signatures from header'); + return false; + } + if (empty($signatures)) { + $this->logStripe('No signatures found with expected scheme'); + return false; + } + + // Check if expected signature is found in list of signatures from + // header + $signedPayload = "{$timestamp}.{$payload}"; + $expectedSignature = $this->computeSignature($signedPayload, $secret); + $signatureFound = false; + foreach ($signatures as $signature) { + if ($expectedSignature === $signature) { + $signatureFound = true; + break; + } + } + + if (!$signatureFound) { + $this->logStripe('No signatures found matching the expected signature for payload'); + return false; + } + + // Check if timestamp is within tolerance + if (($tolerance > 0) && (abs(time() - $timestamp) > $tolerance)) { + $this->logStripe('Timestamp outside the tolerance zone'); + return false; + } + + return true; + } + + /** + * Extracts the timestamp in a signature header. + * + * @param string $header the signature header + * + * @return int the timestamp contained in the header, or -1 if no valid + * timestamp is found + */ + private function getTimestamp($header) + { + $items = explode(',', $header); + + foreach ($items as $item) { + $itemParts = explode('=', $item, 2); + if ('t' === $itemParts[0]) { + if (!is_numeric($itemParts[1])) { + return -1; + } + + return (int) ($itemParts[1]); + } + } + + return -1; + } + + /** + * Extracts the signatures matching a given scheme in a signature header. + * + * @param string $header the signature header + * @param string $scheme the signature scheme to look for + * + * @return array the list of signatures matching the provided scheme + */ + private function getSignatures($header, $scheme) + { + $signatures = []; + $items = explode(',', $header); + + foreach ($items as $item) { + $itemParts = explode('=', $item, 2); + if (trim($itemParts[0]) === $scheme) { + $signatures[] = $itemParts[1]; + } + } + + return $signatures; + } + + /** + * Computes the signature for a given payload and secret. + * + * The current scheme used by Stripe ("v1") is HMAC/SHA-256. + * + * @param string $payload the payload to sign + * @param string $secret the secret used to generate the signature + * + * @return string the signature as a string + */ + private function computeSignature($payload, $secret) + { + return hash_hmac('sha256', $payload, $secret); + } + + /** + * Create transaction log. + * + * @param array $payload + * @param string $type + * @access private + */ + private function createTransactionLog($payload, $type) + { + $event_reference_id = isset($payload['payment_intent']) ? $payload['payment_intent'] : null; + $error = null; + $error_reason = null; + $amount = $payload['amount']; + + if (isset($payload['amount_total'])) { + $amount = $payload['amount_total']; + } + + if (isset($payload['latest_charge'])) { + $event_reference_id = $payload['latest_charge']; + + if (isset($payload['last_payment_error'])) { + $error = $payload['last_payment_error']['code']; + $error_reason = $payload['last_payment_error']['message']; + $event_reference_id = $payload['last_payment_error']['charge']; + } + } + + $this->logStripe('Creating transaction log for %s', $type); + $sql = " + INSERT INTO {$this->txnLogTable} ( + event_id, + event_reference_id, + object, + amount, + currency, + status, + error, + error_reason, + json_payload, + created_at + ) VALUES ( + ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW() + ) + "; + + $sth = $this->server->connection->getStatement($sql); + $ret = $sth->execute(array( + $payload['id'], + $event_reference_id, + $payload['object'], + $amount, + $payload['currency'], + $payload['status'], + $error, + $error_reason, + json_encode($payload) + )); + + if ($ret) { + $this->logStripe('Stored information in Stripe transactions logs table.'); + } + else { + $errorInfo = implode('/', $sth->errorInfo()); + $this->logStripe('Failed to save information in Stripe transactions logs table. (%s)', $errorInfo); + } + } + + private function createTransaction($payload) + { + $customArray = @unserialize(base64_decode((string)$payload['client_reference_id'])); + $customArray = $customArray && is_array($customArray) ? $customArray : array(); + $customData = new Flux_Config($customArray); + $accountID = $customData->get('account_id'); + $serverName = $customData->get('server_name'); + $email = $payload['customer_details']['email']; + $amount = $payload['amount_total']; + $minimum = (float)Flux::config('MinDonationAmount'); + $rate = Flux::config('CreditExchangeRate'); + $credits = floor($amount / $rate); + + // How much will be deposited? + $settleAmount = $amount / 100; + $settleCurrency = $payload['currency']; + + if ($settleAmount && $settleCurrency) { + $this->logStripe('Deposited into PayPal account: %s %s.', $settleAmount, $settleCurrency); + } + + if ($settleCurrency != $this->myCurrencyCode) { + $this->logStripe('Transaction currency not exchangeable, accepting anyways. (recv: %s, expected: %s)', + $settleCurrency, $this->myCurrencyCode); + + $exchangeableCurrency = false; + } + else { + $exchangeableCurrency = true; + } + + // Let's see where the donation credits should go to. + $this->logStripe('Game server name: %s, account ID: %s', + ($serverName ? $serverName : '(absent)'), ($accountID ? $accountID : '(absent)')); + + if (!$accountID || !$serverName) { + $this->logStripe('Account ID and/or game server name absent, cannot exchange for credits.'); + } + elseif (!($servGroup = Flux::getServerGroupByName($serverName))) { + $this->logStripe('Unknown game server "%s", cannot process donation for credits.', $serverName); + return; + } + + if ($servGroup && $exchangeableCurrency) { + $checkServGroup = $this->checkServGroup($accountID); + if (!$checkServGroup) { + $this->logStripe('Unknown account #%s on server %s, cannot exchange for credits.', $accountID, $serverName); + return; + } + } + else { + if (!$servGroup->loginServer->hasCreditsRecord($accountID)) { + $this->logStripe('Identified as first-time donation to the server from this account.'); + } + + if ($amount >= $minimum) { + $sql = "SELECT * FROM {$servGroup->loginDatabase}.{$this->creditsTable} WHERE account_id = ?"; + $sth = $servGroup->connection->getStatement($sql); + $sth->execute(array($accountID)); + $acc = $sth->fetch(); + $balance = (int)$acc->balance; + + $this->logStripe('Updating account credit balance from %s to %s', $balance, $balance + $credits); + $res = $servGroup->loginServer->depositCredits($accountID, $credits, $amount); + + if ($res) { + $this->logStripe('Deposited credits.'); + } + else { + $this->logStripe('Failed to deposit credits.'); + } + } + } + + $sql = " + INSERT INTO {$this->txnTable} ( + event_reference_id, + account_id, + email, + server_name, + credits, + amount, + settleAmount, + settleCurrency, + status, + payment_status, + json_payload, + created_at + ) VALUES ( + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW() + ) + "; + + $sth = $this->server->connection->getStatement($sql); + $ret = $sth->execute(array( + $payload['payment_intent'], + $accountID, + $email, + $serverName, + $credits, + $amount, + $settleAmount, + $settleCurrency, + $payload['status'], + $payload['payment_status'], + json_encode($payload) + )); + + if ($ret) { + if (!trim($serverName)) { + $serverName = '(unknown)'; + } + $this->logStripe('Stored information in Stripe transactions table for server %s.', $serverName); + } + else { + $errorInfo = implode('/', $sth->errorInfo()); + $this->logStripe('Failed to save information in Stripe transactions table. (%s)', $errorInfo); + return; + } + } + + private function checkServGroup() + { + $sql = "SELECT COUNT(account_id) AS acc_id_count FROM {$servGroup->loginDatabase}.login WHERE sex != 'S' AND group_id >= 0 AND account_id = ?"; + $sth = $servGroup->connection->getStatement($sql); + $sth->execute(array($accountID)); + $res = $sth->fetch(); + + if (!$res) { + return false; + } + + return true; + } +} +?> diff --git a/lib/Flux/Template.php b/lib/Flux/Template.php index 577c6f0f5..75f971023 100644 --- a/lib/Flux/Template.php +++ b/lib/Flux/Template.php @@ -22,7 +22,7 @@ class Flux_Template { * @var array */ private $defaultData = array(); - + /** * Request parameters. * @@ -30,7 +30,7 @@ class Flux_Template { * @var Flux_Config */ protected $params; - + /** * Base URI of the entire application. * @@ -38,7 +38,7 @@ class Flux_Template { * @var string */ protected $basePath; - + /** * Module path. * @@ -46,7 +46,7 @@ class Flux_Template { * @var string */ protected $modulePath; - + /** * Module name. * @@ -54,7 +54,7 @@ class Flux_Template { * @var string */ protected $moduleName; - + /** * Theme path. * @@ -62,7 +62,7 @@ class Flux_Template { * @var string */ protected $themePath; - + /** * Theme name. * @@ -70,7 +70,7 @@ class Flux_Template { * @var string */ protected $themeName; - + /** * Action name. Actions exist as modulePath/moduleName/actionName.php. * @@ -78,7 +78,7 @@ class Flux_Template { * @var string */ protected $actionName; - + /** * Action path, would be the path format documented in $actionName. * @@ -86,7 +86,7 @@ class Flux_Template { * @var string */ protected $actionPath; - + /** * View name, this is usually the same as the actionName. * @@ -94,7 +94,7 @@ class Flux_Template { * @var string */ protected $viewName; - + /** * View path, follows a similar (or rather, exact) format like actionPath, * except there would be a themePath and viewName instead. @@ -103,7 +103,7 @@ class Flux_Template { * @var string */ protected $viewPath; - + /** * Header name. The header file would exist under the themePath's top level * and the headerName would simply be the file's basename without the .php @@ -111,9 +111,9 @@ class Flux_Template { * * @access protected * @var string - */ + */ protected $headerName; - + /** * The actual path to the header file. * @@ -121,7 +121,7 @@ class Flux_Template { * @var string */ protected $headerPath; - + /** * The footer name. * Similar to headerName. This name is usually 'footer'. @@ -130,7 +130,7 @@ class Flux_Template { * @var string */ protected $footerName; - + /** * The actual path to the footer file. * @@ -138,7 +138,7 @@ class Flux_Template { * @var string */ protected $footerPath; - + /** * Whether or not to use mod_rewrite-powered clean URLs or just plain old * query strings. @@ -147,7 +147,7 @@ class Flux_Template { * @var string */ protected $useCleanUrls; - + /** * URL of the current module/action being viewed. * @@ -155,7 +155,7 @@ class Flux_Template { * @var string */ protected $url; - + /** * URL of the current module/action being viewed. (including query string) * @@ -164,7 +164,7 @@ class Flux_Template { */ protected $urlWithQs; protected $urlWithQS; // compatibility. - + /** * Module/action for missing action's event. * @@ -172,7 +172,7 @@ class Flux_Template { * @var array */ protected $missingActionModuleAction; - + /** * Module/action for missing view's event. * @@ -180,7 +180,7 @@ class Flux_Template { * @var array */ protected $missingViewModuleAction; - + /** * Inherit view / controllers from another theme ? * @@ -188,7 +188,7 @@ class Flux_Template { * @var Flux_Template */ public $parentTemplate; - + /** * List of themes loaded, use for avoid circular dependencies * @@ -196,7 +196,7 @@ class Flux_Template { * @var array */ static public $themeLoaded = array(); - + /** * HTTP referer. * @@ -204,7 +204,7 @@ class Flux_Template { * @var string */ public $referer; - + /** * Construct new template onbject. * @@ -277,10 +277,10 @@ public function render(array $dataArr = array()) ini_set('zlib.output_compression', 'On'); ini_set('zlib.output_compression_level', (int)Flux::config('GzipCompressionLevel')); } - + $addon = false; $this->actionPath = sprintf('%s/%s/%s.php', $this->modulePath, $this->moduleName, $this->actionName); - + if (!file_exists($this->actionPath)) { foreach (Flux::$addons as $_tmpAddon) { if ($_tmpAddon->respondsTo($this->moduleName, $this->actionName)) { @@ -288,7 +288,7 @@ public function render(array $dataArr = array()) $this->actionPath = sprintf('%s/%s/%s.php', $addon->moduleDir, $this->moduleName, $this->actionName); } } - + if (!$addon) { $this->moduleName = $this->missingActionModuleAction[0]; $this->actionName = $this->missingActionModuleAction[1]; @@ -296,12 +296,12 @@ public function render(array $dataArr = array()) $this->actionPath = sprintf('%s/%s/%s.php', $this->modulePath, $this->moduleName, $this->actionName); } } - + $this->viewPath = $this->themePath(sprintf('%s/%s.php', $this->moduleName, $this->actionName), true); - + if (!file_exists($this->viewPath) && $addon) { $this->viewPath = $addon->getView( $this, $this->moduleName, $this->actionName); - + if ( $this->viewPath === false ) { $this->moduleName = $this->missingViewModuleAction[0]; $this->actionName = $this->missingViewModuleAction[1]; @@ -315,7 +315,7 @@ public function render(array $dataArr = array()) $this->footerPath = $this->themePath($this->footerName.'.php', true); $this->url = $this->url($this->moduleName, $this->actionName); $this->urlWithQS = $this->url; - + if (!empty($_SERVER['QUERY_STRING'])) { if ($this->useCleanUrls) { $this->urlWithQS .= "?{$_SERVER['QUERY_STRING']}"; @@ -325,17 +325,17 @@ public function render(array $dataArr = array()) list ($key,$val) = explode('=', $line, 2); $key = urldecode($key); $val = urldecode($val); - + if ($key != 'module' && $key != 'action') { $this->urlWithQS .= sprintf('&%s=%s', urlencode($key), urlencode($val)); } } } } - + // Compatibility. $this->urlWithQs = $this->urlWithQS; - + // Tidy up! if (Flux::config('OutputCleanHTML')) { $dispatcher = Flux_Dispatcher::getInstance(); @@ -356,34 +356,34 @@ public function render(array $dataArr = array()) ob_start(); } } - + // Merge with default data. $data = array_merge($this->defaultData, $dataArr); - + // Extract data array and make them appear as though they were global // variables from the template. extract($data, EXTR_REFS); - + // Files object. $files = new Flux_Config($_FILES); - + $preprocessorPath = sprintf('%s/main/preprocess.php', $this->modulePath); if (file_exists($preprocessorPath)) { include $preprocessorPath; } - + include $this->actionPath; - + $pageMenuFile = FLUX_ROOT."/modules/{$this->moduleName}/pagemenu/{$this->actionName}.php"; $pageMenuItems = array(); - + // Get the main menu file first (located in the actual module). if (file_exists($pageMenuFile)) { ob_start(); $pageMenuItems = include $pageMenuFile; ob_end_clean(); } - + $addonPageMenuFiles = glob(FLUX_ADDON_DIR."/*/modules/{$this->moduleName}/pagemenu/{$this->actionName}.php"); if ($addonPageMenuFiles) { foreach ($addonPageMenuFiles as $addonPageMenuFile) { @@ -392,17 +392,17 @@ public function render(array $dataArr = array()) ob_end_clean(); } } - + if (file_exists($this->headerPath)) { include $this->headerPath; } - + include $this->viewPath; - + if (file_exists($this->footerPath)) { include $this->footerPath; } - + // Really, tidy up! if (Flux::config('OutputCleanHTML') && !$tidyIgnore && function_exists('tidy_repair_string')) { $content = ob_get_clean(); @@ -410,7 +410,7 @@ public function render(array $dataArr = array()) echo $content; } } - + /** * Returns an array of menu items that should be diplayed from the theme. * Only menu items the current user (and their group level) have access to @@ -425,11 +425,11 @@ public function getMenuItems($adminMenus = false) $defaultAction = Flux_Dispatcher::getInstance()->defaultAction; $menuItems = Flux::config('MenuItems'); $allowedItems = array(); - + if (!($menuItems instanceOf Flux_Config)) { return array(); } - + foreach ($menuItems->toArray() as $categoryName => $menu) { foreach ($menu as $menuName => $menuItem) { $module = array_key_exists('module', $menuItem) ? $menuItem['module'] : false; @@ -452,7 +452,7 @@ public function getMenuItems($adminMenus = false) if (empty($allowedItems[$categoryName])) { $allowedItems[$categoryName] = array(); } - + if ($exturl) { $allowedItems[$categoryName][] = array( 'name' => $menuName, @@ -474,10 +474,10 @@ public function getMenuItems($adminMenus = false) } } } - + return $allowedItems; } - + /** * @see Flux_Template::getMenuItems() */ @@ -485,7 +485,7 @@ public function getAdminMenuItems() { return $this->getMenuItems(true); } - + /** * Get sub-menu items for a particular module. * @@ -498,20 +498,20 @@ public function getSubMenuItems($moduleName = null) $moduleName = $moduleName ? $moduleName : $this->moduleName; $subMenuItems = Flux::config('SubMenuItems'); $allowedItems = array(); - + if (!($subMenuItems instanceOf Flux_Config) || !( ($menus = $subMenuItems->get($moduleName)) instanceOf Flux_Config )) { return array(); } - + foreach ($menus->toArray() as $actionName => $menuName) { if ($auth->actionAllowed($moduleName, $actionName)) { $allowedItems[] = array('name' => $menuName, 'module' => $moduleName, 'action' => $actionName); } } - + return $allowedItems; } - + /** * Get an array of login server names. * @@ -521,7 +521,7 @@ public function getServerNames() { return array_keys(Flux::$loginAthenaGroupRegistry); } - + /** * Determine if more than 1 server exists. * @@ -531,7 +531,7 @@ public function hasManyServers() { return count(Flux::$loginAthenaGroupRegistry) > 1; } - + /** * Obtain the absolute web path of the specified user path. Specify the * path as a relative path. @@ -577,7 +577,7 @@ public function themePath($path, $included = false) $uri = $this->path("{$this->themePath}/{$this->themeName}/{$path}", $included); // normalized basePath. - $base = preg_replace('/(\/+)$/', '', $this->basePath ) . '/'; + $base = preg_replace('/(\/+)$/', '', $this->basePath ) . '/'; $base = preg_quote( $base, '/' ); $chk = FLUX_ROOT .'/'. preg_replace('/^('.$base.')/', '', $uri ); @@ -602,7 +602,7 @@ public function themePath($path, $included = false) return $uri . $frag; } - + /** * Create a URI based on the setting of $useCleanUrls. This will determine * whether or not we will create a mod_rewrite-based clean URL or just a @@ -617,15 +617,15 @@ public function url($moduleName, $actionName = null, $params = array()) $defaultAction = Flux_Dispatcher::getInstance()->defaultAction; $serverProtocol = ''; $serverAddress = ''; - + if ($params instanceOf Flux_Config) { $params = $params->toArray(); } - + if (array_key_exists('_host', $params)) { $_host = $params['_host']; $_https = false; - + if ($_host && ($addr=Flux::config('ServerAddress'))) { if (array_key_exists('_https', $params)) { $_https = $params['_https']; @@ -640,16 +640,16 @@ public function url($moduleName, $actionName = null, $params = array()) $serverProtocol = $_https ? 'https://' : 'http://'; $serverAddress = $addr; } - + unset($params['_host']); - + if (array_key_exists('_https', $params)) { unset($params['_https']); } } - + $queryString = ''; - + if (count($params)) { $queryString .= Flux::config('UseCleanUrls') ? '?' : '&'; foreach ($params as $param => $value) { @@ -657,7 +657,7 @@ public function url($moduleName, $actionName = null, $params = array()) } $queryString = rtrim($queryString, '&'); } - + if ($this->useCleanUrls) { if ($actionName && $actionName != $defaultAction) { $url = sprintf('%s/%s/%s/%s', $this->basePath, $moduleName, $actionName, $queryString); @@ -676,7 +676,7 @@ public function url($moduleName, $actionName = null, $params = array()) } return $serverProtocol.preg_replace('&/{2,}&', '/', "$serverAddress/$url"); } - + /** * Format currency strings. * @@ -694,7 +694,7 @@ public function formatCurrency($number) ); return $amount; } - + /** * Format a MySQL DATE column according to the DateFormat config. * @@ -707,7 +707,7 @@ public function formatDate($date = null) $ts = $date ? strtotime($date) : time(); return date(Flux::config('DateFormat'), $ts); } - + /** * Format a MySQL DATETIME column according to the DateTimeFormat config. * @@ -720,7 +720,7 @@ public function formatDateTime($dateTime = null) $ts = $dateTime ? strtotime($dateTime) : time(); return date(Flux::config('DateTimeFormat'), $ts); } - + /** * Create a series of select fields matching a MySQL DATE format. * @@ -738,14 +738,14 @@ public function dateField($name, $value = null, $fowardYears = null, $backwardYe if(!isset($backwardYears)) { $backwardYears = (int)Flux::config('BackwardYears'); } - + $ts = $value && !preg_match('/^0000-00-00(?: 00:00:00)?$/', $value) ? strtotime($value) : time(); $year = ($year =$this->params->get("{$name}_year")) ? $year : date('Y', $ts); $month = ($month=$this->params->get("{$name}_month")) ? $month : date('m', $ts); $day = ($day =$this->params->get("{$name}_day")) ? $day : date('d', $ts); $fw = $year + $fowardYears; $bw = $year - $backwardYears; - + // Get years. $years = sprintf(''; - + // Get months. $months = sprintf(''; - + // Get days. $days = sprintf(''; - + return sprintf('%s-%s-%s', $years, $months, $days); } - + /** * Create a series of select fields matching a MySQL DATETIME format. * @@ -799,7 +799,7 @@ public function dateTimeField($name, $value = null) $hour = date('H', $ts); $minute = date('i', $ts); $second = date('s', $ts); - + // Get hours. $hours = sprintf(''; - + // Get minutes. $minutes = sprintf(''; - + // Get seconds. $seconds = sprintf(''; - + return sprintf('%s @ %s:%s:%s', $dateField, $hours, $minutes, $seconds); } - + /** * Returns "up" or "down" in a span HTML element with either the class * .up or .down, based on the value of $bool. True returns up, false @@ -852,7 +852,7 @@ public function serverUpDown($bool) $class = $bool ? 'up' : 'down'; return sprintf('%s', $class, $bool ? 'Online' : 'Offline'); } - + /** * Redirect client to another location. Script execution is terminated * after instructing the client to redirect. @@ -864,11 +864,11 @@ public function redirect($location = null) if (is_null($location)) { $location = $this->basePath; } - + header("Location: $location"); exit; } - + /** * Guess the HTTP server's current full URL. * @@ -880,18 +880,18 @@ public function entireUrl($withRequest = true) $proto = empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === "off" ? 'http://' : 'https://'; $hostname = empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] : $_SERVER['HTTP_HOST']; $request = $_SERVER['REQUEST_URI']; - + if ($withRequest) { $url = $proto.$hostname.$request; } else { $url = $proto.$hostname.'/'.$this->basePath; } - + $url = rtrim(preg_replace('&/{2,}&', '/', $url), '/'); return $url; } - + /** * Convenience method for retrieving a paginator instance. * @@ -905,7 +905,7 @@ public function getPaginator($total, array $options = array()) $paginator = new Flux_Paginator($total, $this->url($this->moduleName, $this->actionName, array('_host' => false)), $options); return $paginator; } - + /** * Link to an account view page. * @@ -924,7 +924,7 @@ public function linkToAccount($accountID, $text) return false; } } - + /** * Link to an account search. * @@ -943,7 +943,7 @@ public function linkToAccountSearch($params, $text) return false; } } - + /** * Link to a character view page. * @@ -959,7 +959,7 @@ public function linkToCharacter($charID, $text, $server = null) if ($server) { $params['preferred_server'] = $server; } - + $url = $this->url('character', 'view', $params); return sprintf('%s', $url, htmlspecialchars($text)); } @@ -967,7 +967,7 @@ public function linkToCharacter($charID, $text, $server = null) return false; } } - + /** * Deny entry to a page if called. This method should be used from a module * script, and no where else. @@ -977,7 +977,7 @@ public function deny() $location = $this->url('unauthorized'); $this->redirect($location); } - + /** * Get the full gender string from a gender letter (e.g. M for Male). * @@ -1002,7 +1002,7 @@ public function genderText($gender) break; } } - + /** * Get the account state name corresponding to the state number. * @@ -1014,7 +1014,7 @@ public function accountStateText($state) { $text = false; $state = (int)$state; - + switch ($state) { case 0: $text = Flux::message('AccountStateNormal'); @@ -1025,7 +1025,7 @@ public function accountStateText($state) $class = 'state-permanently-banned'; break; } - + if ($text) { $text = htmlspecialchars($text); return sprintf('%s', $class, $text); @@ -1034,7 +1034,7 @@ public function accountStateText($state) return false; } } - + /** * Get the job class name from a job ID. * @@ -1046,7 +1046,7 @@ public function jobClassText($id) { return Flux::getJobClass($id); } - + /** * Return hidden input fields containing module and action names based on * the setting of UseCleanUrls. @@ -1057,7 +1057,7 @@ public function jobClassText($id) * @access public */ public function moduleActionFormInputs($moduleName, $actionName = null) - { + { $inputs = ''; if (!Flux::config('UseCleanUrls')) { if (!$actionName) { @@ -1069,7 +1069,7 @@ public function moduleActionFormInputs($moduleName, $actionName = null) } return $inputs; } - + /** * Get the homun class name from a class ID. * @@ -1092,7 +1092,7 @@ public function itemTypeText($id) { return Flux::getItemType($id); } - + public function itemSubTypeText($id1, $id2) { if($id1 == 'Weapon' || $id1 == 'Ammo' || $id1 == 'Card') @@ -1100,12 +1100,12 @@ public function itemSubTypeText($id1, $id2) else return false; } - + public function itemRandOption($id, $value) { return sprintf(Flux::getRandOption($id), $value); } - + /** * Get the item information from splitting a delimiter * Used for renewal ATK and MATK as well as equip_level_min and equip_level_max. @@ -1125,7 +1125,7 @@ public function itemFieldExplode($object, $field, $delimiter, $inputs) } return $object; } - + /** * Get the equip location combination name from an equip location combination. * @@ -1137,7 +1137,7 @@ public function equipLocationCombinationText($id) { return Flux::getEquipLocationCombination($id); } - + /** * * @@ -1147,15 +1147,15 @@ public function emblem($guildID, $serverName = null, $athenaServerName = null) if (!$serverName) { $serverName = Flux::$sessionData->loginAthenaGroup->serverName; } - + if (!$athenaServerName) { $athenaServerName = Flux::$sessionData->getAthenaServer(Flux::$sessionData->athenaServerName); } - + return $this->url('guild', 'emblem', array('login' => $serverName, 'charmap' => $athenaServerName, 'id' => $guildID)); } - + /** * Redirect to login page if the user is not currently logged in. */ @@ -1164,7 +1164,7 @@ public function loginRequired($message = null) $dispatcher = Flux_Dispatcher::getInstance(); $dispatcher->loginRequired($this->basePath, $message); } - + /** * Link to a item view page. * @@ -1180,7 +1180,7 @@ public function linkToItem($itemID, $text, $server = null) if ($server) { $params['preferred_server'] = $server; } - + $url = $this->url('item', 'view', $params); return sprintf('%s', $url, htmlspecialchars($text)); } @@ -1188,7 +1188,7 @@ public function linkToItem($itemID, $text, $server = null) return false; } } - + /** * */ @@ -1197,17 +1197,17 @@ public function displayScript($scriptText) $lines = !empty($scriptText) ? preg_split('/\s+|<|>|\[|\]/', $scriptText, -1, PREG_SPLIT_NO_EMPTY) : []; $text = ''; $script = array(); - + foreach ($lines as $num => $line) { $text .= "$line\n"; $lineNum = sprintf('%d', $num + 1); $lineCode = sprintf('%s', htmlspecialchars($line)); $script[] = sprintf('%s %s
', $lineNum, $lineCode); } - + return trim($text) == '' ? '' : implode("\n", $script); } - + /** * */ @@ -1227,7 +1227,7 @@ public function banTypeText($banType) return Flux::message('UnknownLabel'); } } - + /** * */ @@ -1235,35 +1235,35 @@ public function equippableJobs($equipJob) { $jobs = array(); $equipJobs = Flux::getEquipJobsList(); - + foreach ($equipJob as $name) { $jobs[] = $equipJobs[$name]; if($name == 'job_all') break; } - + return $jobs; } - + /** * */ public function GetJobsList($isRenewal) { $jobs = Flux::getEquipJobsList($isRenewal); - + return $jobs; } - + /** * */ public function GetClassList($isRenewal) { $jobs = Flux::getEquipUpperList($isRenewal); - + return $jobs; } - + /** * */ @@ -1271,14 +1271,14 @@ public function tradeRestrictions($list) { $restrictions = array(); $Restrictions = Flux::getTradeRestrictionList(); - + foreach ($list as $name) { $restrictions[] = $Restrictions[$name]; } - + return $restrictions; } - + /** * */ @@ -1286,11 +1286,11 @@ public function itemsFlags($list) { $flags = array(); $Flags = Flux::getItemFlagList(); - + foreach ($list as $name) { $flags[] = $Flags[$name]; } - + return $flags; } @@ -1309,7 +1309,7 @@ public function linkToMonster($monsterID, $text, $server = null) if ($server) { $params['preferred_server'] = $server; } - + $url = $this->url('monster', 'view', $params); return sprintf('%s', $url, htmlspecialchars($text)); } @@ -1317,7 +1317,7 @@ public function linkToMonster($monsterID, $text, $server = null) return false; } } - + /** * */ @@ -1339,7 +1339,7 @@ public function equipLocations($equipLoc) else return false; } - + /** * */ @@ -1347,12 +1347,12 @@ public function equipUpper($equipUpper, $isRenewal = 1) { $upper = array(); $table = Flux::getEquipUpperList($isRenewal); - + foreach ($equipUpper as $name) { $upper[] = $table[$name]; if($name == 'class_all') break; } - + return $upper; } @@ -1371,7 +1371,7 @@ public function linkToGuild($guild_id, $text, $server = null) if ($server) { $params['preferred_server'] = $server; } - + $url = $this->url('guild', 'view', $params); return sprintf('%s', $url, htmlspecialchars($text)); } @@ -1379,18 +1379,24 @@ public function linkToGuild($guild_id, $text, $server = null) return false; } } - + /** * */ public function donateButton($amount) { ob_start(); - include FLUX_DATA_DIR.'/paypal/button.php'; + if (in_array("paypal", Flux::config('PaymentGateway')->toArray())) { + include FLUX_DATA_DIR.'/paypal/button.php'; + } + if (in_array("stripe", Flux::config('PaymentGateway')->toArray())) { + include FLUX_DATA_DIR.'/stripe/button.php'; + } + $button = ob_get_clean(); return $button; } - + /** * */ @@ -1399,26 +1405,26 @@ public function shopItemImage($shopItemID, $serverName = null, $athenaServerName if (!$serverName) { $serverName = Flux::$sessionData->loginAthenaGroup->serverName; } - + if (!$athenaServerName) { $athenaServerName = Flux::$sessionData->getAthenaServer(Flux::$sessionData->athenaServerName); } - + if (!$serverName || !$athenaServerName) { return false; } - + $dir = FLUX_DATA_DIR."/itemshop/$serverName/$athenaServerName"; $exts = implode('|', array_map('preg_quote', Flux::config('ShopImageExtensions')->toArray())); $imgs = glob("$dir/$shopItemID.*"); - + if (is_array($imgs)) { $files = preg_grep("/\.($exts)$/", $imgs); } else { $files = array(); } - + if (empty($files)) { return false; } @@ -1428,7 +1434,7 @@ public function shopItemImage($shopItemID, $serverName = null, $athenaServerName return preg_replace('&/{2,}&', '/', "{$this->basePath}/$imageFile"); } } - + /** * */ @@ -1436,7 +1442,7 @@ public function iconImage($itemID) { $path = sprintf(FLUX_DATA_DIR."/items/icons/".Flux::config('ItemIconNameFormat'), $itemID); $link = preg_replace('&/{2,}&', '/', "{$this->basePath}/$path"); - + if(Flux::config('DivinePrideIntegration') && !file_exists($path)) { $download_link = "https://static.divine-pride.net/images/items/item/$itemID.png"; $data = get_headers($download_link, true); @@ -1446,7 +1452,7 @@ public function iconImage($itemID) } return file_exists($path) ? $link : false; } - + /** * */ @@ -1454,7 +1460,7 @@ public function itemImage($itemID) { $path = sprintf(FLUX_DATA_DIR."/items/images/".Flux::config('ItemImageNameFormat'), $itemID); $link = preg_replace('&/{2,}&', '/', "{$this->basePath}/$path"); - + if(Flux::config('DivinePrideIntegration') && !file_exists($path)) { $download_link = "https://static.divine-pride.net/images/items/collection/$itemID.png"; $data = get_headers($download_link, true); @@ -1472,7 +1478,7 @@ public function monsterImage($monsterID) { $path = sprintf(FLUX_DATA_DIR."/monsters/".Flux::config('MonsterImageNameFormat'), $monsterID); $link = preg_replace('&/{2,}&', '/', "{$this->basePath}/$path"); - + if(Flux::config('DivinePrideIntegration') && !file_exists($path)) { $download_link = "https://static.divine-pride.net/images/mobs/png/$monsterID.png"; $data = get_headers($download_link, true); @@ -1482,7 +1488,7 @@ public function monsterImage($monsterID) } return file_exists($path) ? $link : false; } - + /** * */ @@ -1492,7 +1498,7 @@ public function jobImage($gender, $jobID) $link = preg_replace('&/{2,}&', '/', "{$this->basePath}/$path"); return file_exists($path) ? $link : false; } - + /** * */ diff --git a/modules/donate/admin.php b/modules/donate/admin.php new file mode 100644 index 000000000..9317be75f --- /dev/null +++ b/modules/donate/admin.php @@ -0,0 +1,11 @@ +loginDatabase}.$transactionTable WHERE DATE('created_at') = CURDATE()"; +$sth = $server->connection->getStatement($sql); +$sth->execute(); +$donates = $sth->fetch(); +?> diff --git a/modules/donate/history.php b/modules/donate/history.php index 3b337e309..3f621628a 100644 --- a/modules/donate/history.php +++ b/modules/donate/history.php @@ -3,24 +3,25 @@ $this->loginRequired(); -$txnTable = Flux::config('FluxTables.TransactionTable'); +$txnPaypalTable = Flux::config('FluxTables.TransactionTable'); +$txnStripeTable = Flux::config('FluxTables.StripeTransactionTable'); +$account_id = $session->account->account_id; /** Completed Transactions **/ +$sqlpartial = "WHERE cp.account_id = ? AND st.account_id = cp.account_id AND cp.hold_until IS NULL AND cp.payment_status = 'Completed' AND st.payment_status = 'paid' "; +$sqlpartial .= "ORDER BY cp.payment_date DESC"; -$sqlpartial = "WHERE account_id = ? AND hold_until IS NULL AND payment_status = 'Completed' "; -$sqlpartial .= "ORDER BY payment_date DESC"; - -$sql = "SELECT COUNT(id) AS total FROM {$server->loginDatabase}.$txnTable $sqlpartial"; +$sql = "SELECT COUNT(*) AS total FROM {$server->loginDatabase}.$txnPaypalTable AS cp, {$server->loginDatabase}.$txnStripeTable AS st $sqlpartial"; $sth = $server->connection->getStatement($sql); -$sth->execute(array($session->account->account_id)); +$sth->execute(array($account_id)); $completedTotal = $sth->fetch()->total; $col = "*"; -$sql = "SELECT $col FROM {$server->loginDatabase}.$txnTable $sqlpartial"; +$sql = "SELECT $col FROM {$server->loginDatabase}.$txnPaypalTable AS cp, {$server->loginDatabase}.$txnStripeTable AS st $sqlpartial"; $sth = $server->connection->getStatement($sql); -$sth->execute(array($session->account->account_id)); +$sth->execute(array($account_id)); $completedTxn = $sth->fetchAll(); /** Held Transactions **/ @@ -28,34 +29,34 @@ $sqlpartial = "WHERE account_id = ? AND hold_until IS NOT NULL AND payment_status = 'Completed' "; $sqlpartial .= "ORDER BY payment_date DESC"; -$sql = "SELECT COUNT(id) AS total FROM {$server->loginDatabase}.$txnTable $sqlpartial"; +$sql = "SELECT COUNT(id) AS total FROM {$server->loginDatabase}.$txnPaypalTable $sqlpartial"; $sth = $server->connection->getStatement($sql); -$sth->execute(array($session->account->account_id)); +$sth->execute(array($account_id)); $heldTotal = $sth->fetch()->total; $col = "*"; -$sql = "SELECT $col FROM {$server->loginDatabase}.$txnTable $sqlpartial"; +$sql = "SELECT $col FROM {$server->loginDatabase}.$txnPaypalTable $sqlpartial"; $sth = $server->connection->getStatement($sql); -$sth->execute(array($session->account->account_id)); +$sth->execute(array($account_id)); $heldTxn = $sth->fetchAll(); /** Failed Transactions **/ -$sqlpartial = "WHERE account_id = ? AND hold_until IS NULL AND payment_status = 'Completed' "; -$sqlpartial .= "AND credits < 1 ORDER BY payment_date DESC"; +$sqlpartial = "WHERE cp.account_id = ? AND st.account_id = cp.account_id AND cp.hold_until IS NULL AND cp.payment_status = 'Completed' OR st.payment_status <> 'paid' "; +$sqlpartial .= "AND cp.credits < 1 ORDER BY cp.payment_date DESC"; -$sql = "SELECT COUNT(id) AS total FROM {$server->loginDatabase}.$txnTable $sqlpartial"; +$sql = "SELECT COUNT(*) AS total FROM {$server->loginDatabase}.$txnPaypalTable AS cp, {$server->loginDatabase}.$txnStripeTable AS st $sqlpartial"; $sth = $server->connection->getStatement($sql); -$sth->execute(array($session->account->account_id)); +$sth->execute(array($account_id)); $failedTotal = $sth->fetch()->total; $col = "*"; -$sql = "SELECT $col FROM {$server->loginDatabase}.$txnTable $sqlpartial"; +$sql = "SELECT $col FROM {$server->loginDatabase}.$txnPaypalTable AS cp, {$server->loginDatabase}.$txnStripeTable AS st $sqlpartial"; $sth = $server->connection->getStatement($sql); -$sth->execute(array($session->account->account_id)); +$sth->execute(array($account_id)); $failedTxn = $sth->fetchAll(); ?> diff --git a/modules/donate/notify.php b/modules/donate/notify.php index 586049f19..ad58855af 100644 --- a/modules/donate/notify.php +++ b/modules/donate/notify.php @@ -1,10 +1,18 @@ process(); + if (in_array("paypal", Flux::config('PaymentGateway')->toArray())) { + $request = new Flux_PaypalNotifyRequest($_POST); + $request->process(); + } + + if (in_array("stripe", Flux::config('PaymentGateway')->toArray())) { + $request = new Flux_StripeNotifyRequest($server); + $request->process(); + } } exit; ?> diff --git a/themes/default/donate/admin.php b/themes/default/donate/admin.php new file mode 100644 index 000000000..e6bce3039 --- /dev/null +++ b/themes/default/donate/admin.php @@ -0,0 +1,7 @@ + + + +Url to be registered in Hosted endpoints (Stripe): url('donate', 'stripenotify', array('_host' => true)) ?>
Donates today: count); ?>
diff --git a/themes/default/donate/complete.php b/themes/default/donate/complete.php old mode 100644 new mode 100755 diff --git a/themes/default/donate/history.php b/themes/default/donate/history.php old mode 100644 new mode 100755 index 73aa7942c..37f1a9cc1 --- a/themes/default/donate/history.php +++ b/themes/default/donate/history.php @@ -12,15 +12,29 @@By donating, you're supporting the costs of running this server and maintaining it. In return, you will be rewarded donation credits that you may use to purchase items from our item shop.
All donations towards us are received by PayPal, but don't worry! Even if you don't have an account with PayPal, you can still use your credit card to donate!
- +All donations towards us are received by , but don't worry! Even if you don't have an account with , you can still use your credit card to donate!
+ - +To prevent fraudulent payments, our server currently locks the crediting process for hours @@ -42,7 +48,7 @@ - +
-When you're ready to donate, click the big “Donate” button to proceed with your transaction. - (You can choose to donate from your existing PayPal balance or use your credit card if you don't have an account).
- -- — - credits - — -
- -Amount: - - formatCurrency($donationAmount) ?> - - -
-- (Reset Amount) -
-donateButton($donationAmount) ?>
+ toArray())): ?> +When you're ready to donate, click the big “Donate” button to proceed with your transaction. + (You can choose to donate from your existing PayPal balance or use your credit card if you don't have an account).
+ + toArray())): ?> +Or you can donate with Stripe and try others methods
+ + ++ — + credits + — +
+ +Amount: + + formatCurrency($donationAmount) ?> + + +
++ (Reset Amount) +
+donateButton($donationAmount) ?>
diff --git a/themes/default/donate/notify.php b/themes/default/donate/notify.php old mode 100644 new mode 100755 diff --git a/themes/default/donate/trusted.php b/themes/default/donate/trusted.php old mode 100644 new mode 100755 diff --git a/themes/default/donate/update.php b/themes/default/donate/update.php old mode 100644 new mode 100755