TurnstilePriceCategory.php (9309B)
1 <?php 2 3 /** 4 * @file 5 * Price category structure for the GNU Taler Turnstile module. 6 */ 7 8 namespace Drupal\taler_turnstile\Entity; 9 10 use Drupal\Core\Config\Entity\ConfigEntityBase; 11 use Drupal\Core\StringTranslation\StringTranslationTrait; 12 13 /** 14 * Defines the Price Category entity. 15 * 16 * @ConfigEntityType( 17 * id = "taler_turnstile_price_category", 18 * label = @Translation("Price Category"), 19 * handlers = { 20 * "list_builder" = "Drupal\taler_turnstile\PriceCategoryListBuilder", 21 * "form" = { 22 * "add" = "Drupal\taler_turnstile\Form\PriceCategoryForm", 23 * "edit" = "Drupal\taler_turnstile\Form\PriceCategoryForm", 24 * "delete" = "Drupal\taler_turnstile\Form\PriceCategoryDeleteForm" 25 * } 26 * }, 27 * config_prefix = "taler_turnstile_price_category", 28 * admin_permission = "administer price categories", 29 * entity_keys = { 30 * "id" = "id", 31 * "label" = "label" 32 * }, 33 * links = { 34 * "collection" = "/admin/structure/taler-turnstile-price-categories", 35 * "edit-form" = "/admin/structure/taler-turnstile-price-categories/{price_category}/edit", 36 * "delete-form" = "/admin/structure/taler-turnstile-price-categories/{price_category}/delete" 37 * }, 38 * config_export = { 39 * "id", 40 * "label", 41 * "description", 42 * "prices" 43 * } 44 * ) 45 */ 46 class TurnstilePriceCategory extends ConfigEntityBase { 47 48 /** 49 * For i18n, gives us the t() function. 50 */ 51 use StringTranslationTrait; 52 53 /** 54 * The price category ID. 55 * 56 * @var string 57 */ 58 protected $id; 59 60 /** 61 * The price category label. 62 * 63 * @var string 64 */ 65 protected $label; 66 67 /** 68 * The price category description. 69 * 70 * @var string 71 */ 72 protected $description; 73 74 /** 75 * The prices array. 76 * 77 * Structure: ['subscription_id' => ['currency_code' => 'price']] 78 * 79 * @var array 80 */ 81 protected $prices = []; 82 83 /** 84 * Gets the description. 85 * 86 * @return string 87 * The description. 88 */ 89 public function getDescription() { 90 return $this->description; 91 } 92 93 /** 94 * Gets a brief hint to display about non-subscriber prices. 95 * 96 * @return string 97 * A hint about the price 98 */ 99 public function getPriceHint() { 100 $prices = $this->getPrices(); 101 $nosub = $prices['%none%']; 102 $rval = NULL; 103 foreach ($nosub as $currency => $price) { 104 if (NULL === $rval) 105 { 106 $rval = "$price $currency"; 107 } 108 else 109 { 110 $rval = "$price $currency, " . $rval; 111 } 112 } 113 return $rval ?? $this->t(/* Need to buy a subscription */ 'Not sold individually'); 114 } 115 116 /** 117 * Gets a brief hint to display about applicable subscriptions. 118 * 119 * @return string 120 * A hint about subscriptions 121 */ 122 public function getSubscriptionHint() { 123 $prices = $this->getPrices(); 124 $rval = NULL; 125 foreach ($prices as $subscription => $details) { 126 if ('%none%' === $subscription) 127 continue; 128 if (NULL === $rval) 129 { 130 $rval = $subscription; 131 } 132 else 133 { 134 $rval = $subscription . ", " . $rval; 135 } 136 } 137 return $rval ?? $this->t(/* No subscriptions */ 'None'); 138 } 139 140 /** 141 * Gets all prices. 142 * 143 * @return array 144 * The prices array. 145 */ 146 public function getPrices() { 147 return $this->prices ?: []; 148 } 149 150 /** 151 * Return the different subscriptions that this price category 152 * has for which the resulting payment amount is zero (thus, 153 * exlude subscriptions that would merely yield a discount). 154 * 155 * @return array 156 * The names (slugs) of the subscriptions 157 * that for this price category would yield a price of zero; 158 * note that empty prices do NOT count as zero but infinity! 159 */ 160 public function getFullSubscriptions(): array { 161 $subscriptions = []; 162 foreach ($this->getPrices() as $tokenFamilySlug => $currencyMap) { 163 foreach ($currencyMap as $currencyCode => $price) { 164 if ( (is_numeric ($price)) && 165 (0.0 == (float) $price) ) { 166 $subscriptions[] = $tokenFamilySlug; 167 break; 168 } 169 } 170 } 171 return $subscriptions; 172 } 173 174 /** 175 * Return the different payment choices in a way suitable 176 * for GNU Taler v1 contracts. 177 * 178 * @param array $expirations 179 * Times when each possible subscription type expires. 180 * @return array 181 * Structure suitable for the choices array in the v1 contract 182 */ 183 public function getPaymentChoices(array $subscriptions): array { 184 $max_cache = time() + 3600; 185 $choices = []; 186 187 foreach ($this->getPrices() as $tokenFamilySlug => $currencyMap) { 188 if ("%none%" !== $tokenFamilySlug) { 189 $subscription = $subscriptions[$tokenFamilySlug]; 190 $expi = $subscription['valid_before_s'] ?? 0; 191 if ($expi < time()) { 192 \Drupal::logger('taler_turnstile')->info('Subscription category @slug expired at @expire, skipping it.', ['@slug' => $tokenFamilySlug, '@expire' => $expi]); 193 continue; // already expired 194 } 195 $max_cache = min ($max_cache, 196 $expi); 197 } 198 foreach ($currencyMap as $currencyCode => $price) { 199 $inputs = []; 200 if ("%none%" !== $tokenFamilySlug) 201 { 202 $inputs[] = [ 203 'type' => 'token', 204 'token_family_slug' => $tokenFamilySlug, 205 'count' => 1, 206 ]; 207 $description = $this->t('Pay in @currency with subscription', [ 208 '@currency' => $currencyCode, 209 ], [ 210 'langcode' => 'en', // force English version here! 211 ]); 212 $description_i18n = $this->buildTranslationMap ( 213 'Pay in @currency with subscription', 214 ['@currency' => $currencyCode] 215 ); 216 $choices[] = [ 217 'amount' => $currencyCode . ':' . $price, 218 'description' => 'Pay in ' . $currencyCode . ' with subscription', 219 // 'description_i18n' => $description_i18n, 220 'inputs' => $inputs, 221 ]; 222 $subscription_price = $this->getSubscriptionPrice ($tokenFamilySlug, $currencyCode); 223 if ($subscription_price !== NULL) { 224 // This subscription can be bought. 225 $outputs = []; 226 $outputs[] = [ 227 'type' => 'token', 228 'token_family_slug' => $tokenFamilySlug, 229 'count' => 1, 230 ]; 231 $description = $this->t('Buy subscription in @currency', [ 232 '@currency' => $currencyCode, 233 ]); 234 $description_i18n = $this->buildTranslationMap ( 235 'Buy subscription in @currency', 236 ['@currency' => $currencyCode] 237 ); 238 $choices[] = [ 239 'amount' => $currencyCode . ':' . ((float) $subscription_price + 240 (float) $price), 241 'description' => $description, 242 'description_i18n' => $description_i18n, 243 'outputs' => $outputs, 244 ]; 245 } 246 } 247 else // case for no subscription 248 { 249 $description = $this->t('Pay in @currency', [ 250 '@currency' => $currencyCode, 251 ]); 252 $description_i18n = $this->buildTranslationMap ( 253 'Pay in @currency', 254 ['@currency' => $currencyCode] 255 ); 256 $choices[] = [ 257 'amount' => $currencyCode . ':' . (float) $price, 258 'description' => $description, 259 'description_i18n' => $description_i18n, 260 'inputs' => $inputs, 261 ]; 262 } 263 } // for each possible currency 264 } // for each type of subscription 265 266 return $choices; 267 } 268 269 /** 270 * Sets the prices array. 271 * 272 * @param array $prices 273 * The prices array. 274 * 275 * @return $this 276 */ 277 public function setPrices(array $prices) { 278 $this->prices = $prices; 279 return $this; 280 } 281 282 /** 283 * Determine the price of the given type of subscription 284 * in the given currency. 285 * 286 * @param string $tokenFamilySlug 287 * The slug of the token family 288 * @param string $currencyCode 289 * Currency code in which a price quote is desired 290 * 291 * @return string|null 292 * The subscription price (will map to a float), NULL on error 293 */ 294 private function getSubscriptionPrice (string $tokenFamilySlug, string $currencyCode) { 295 $config = \Drupal::config('taler_turnstile.settings'); 296 $subscriptions_prices = $config->get('subscription_prices') ?? []; 297 $subscription_prices = $subscriptions_prices[$tokenFamilySlug] ?? []; 298 $subscription_price = $subscription_prices[$currencyCode] ?? NULL; 299 return $subscription_price; 300 } 301 302 303 /** 304 * Build a translation map for all enabled languages. 305 * 306 * @param string $string 307 * The translatable string. 308 * @param array $args 309 * Placeholder replacements. 310 * 311 * @return array 312 * Map of language codes to translated strings. 313 */ 314 private function buildTranslationMap(string $string, array $args = []): array { 315 $translations = []; 316 $language_manager = \Drupal::languageManager(); 317 318 foreach ($language_manager->getLanguages() as $langcode => $language) { 319 $translation = $this->t($string, $args, [ 320 'langcode' => $langcode, 321 ]); 322 $translations[$langcode] = (string) $translation; 323 } 324 return $translations; 325 } 326 327 }