• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla / fx-private-relay / d3128616-238d-446e-82c5-ab66cd38ceaf

09 May 2024 06:22PM CUT coverage: 84.07% (-0.6%) from 84.64%
d3128616-238d-446e-82c5-ab66cd38ceaf

push

circleci

web-flow
Merge pull request #4684 from mozilla/enable-flak8-bandit-checks-mpp-3802

fix MPP-3802: stop ignoring bandit security checks

3601 of 4734 branches covered (76.07%)

Branch coverage included in aggregate %.

74 of 158 new or added lines in 24 files covered. (46.84%)

5 existing lines in 5 files now uncovered.

14686 of 17018 relevant lines covered (86.3%)

10.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.3
/privaterelay/plans.py
1
"""
2
Paid plans for Relay
3

4
There is currently a free plan and 3 paid plans:
5

6
* free - limited random email masks, one reply
7
* premium - unlimited email masks, replies, and a custom subdomain
8
* phones - premium, plus a phone mask
9
* bundle - premium and phones, plus Mozilla VPN
10

11
These functions get the details of the paid plans:
12

13
* get_premium_country_language_mapping
14
  * get_premium_countries
15
* get_phone_country_language_mapping
16
* get_bundle_country_language_mapping
17

18
They all return a PlanCountryLangMapping dict, which has this structure:
19

20
{
21
  "AT": {
22
    "*": {
23
      "monthly": {
24
        "id": "price_1LYC79JNcmPzuWtRU7Q238yL",
25
        "price": 1.99,
26
        "currency": "EUR",
27
      },
28
      "yearly": {
29
        "id": "price_1LYC7xJNcmPzuWtRcdKXCVZp",
30
        "price": 0.99,
31
        "currency": "EUR",
32
      },
33
    },
34
  },
35
  ...
36
}
37

38
This says that Austria (RelayCountryStr "AT") with any language ("*")
39
has a monthly and a yearly plan. The monthly plan has a Stripe ID of
40
"price_1LYC79JNcmPzuWtRU7Q238yL", and costs €1.99 (CurrencyStr "EUR"). The yearly
41
plan has a Stripe ID of "price_1LYC7xJNcmPzuWtRcdKXCVZp", and costs €11.88 a year,
42
equivalent to €0.99 a month.
43

44
The top-level keys say which countries are supported. The function get_premium_countries
45
returns these as a set, when the rest of the data is unneeded.
46

47
The second-level keys are the languages for that country. When all languages in that
48
country have the same plan, the single entry is "*". When the country is known but
49
the language is not, or is not one of the listed languages, the first language is the
50
default for that country.
51

52
The third-level keys are the plan periods. Premium and phones are available on
53
monthly and yearly periods, and bundle is yearly only.
54

55
The raw data is stored in two dicts:
56
* _STRIPE_PLAN_DATA
57
* _RELAY_PLANS
58

59
These are extended to support more countries, languages, plans, etc. They are parsed
60
on first use to create the PlanCountryLangMapping, and served from cache on later uses.
61
"""
62

63
from copy import deepcopy
1✔
64
from functools import lru_cache
1✔
65
from typing import Literal, TypedDict, get_args
1✔
66

67
from django.conf import settings
1✔
68

69
#
70
# Public types
71
#
72

73
# ISO 4217 currency identifier
74
# See https://en.wikipedia.org/wiki/ISO_4217
75
CurrencyStr = Literal[
1✔
76
    "CHF",  # Swiss Franc, Fr. or fr.
77
    "CZK",  # Czech koruna, Kč
78
    "DKK",  # Danish krone, kr.
79
    "EUR",  # Euro, €
80
    "PLN",  # Polish złoty, zł
81
    "USD",  # US Dollar, $
82
]
83

84
# ISO 639 language codes handled by Relay
85
# These are the 4th-level keys in _RELAY_PLANS[$PLAN][by_country_and_lang][$COUNTRY],
86
# and are unrelated to the supported languages in Pontoon.
87
#
88
# Use the 2-letter ISO 639-1 code if available, otherwise the 3-letter ISO 639-2 code.
89
# See https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
90
# and https://www.loc.gov/standards/iso639-2/php/English_list.php
91
LanguageStr = Literal[
1✔
92
    "de",  # German
93
    "fr",  # French
94
    "it",  # Italian
95
    "nl",  # Dutch
96
]
97

98
# ISO 3166 country codes handled by Relay
99
# Specifically, the two-letter ISO 3116-1 alpha-2 codes
100
# See https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes
101
# and https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
102
CountryStr = Literal[
1✔
103
    "AT",  # Austria
104
    "BE",  # Belgium
105
    "BG",  # Bulgaria
106
    "CA",  # Canada
107
    "CH",  # Switzerland
108
    "CY",  # Cyprus
109
    "CZ",  # Czech Republic / Czechia
110
    "DE",  # Germany
111
    "DK",  # Denmark
112
    "EE",  # Estonia
113
    "ES",  # Spain
114
    "FI",  # Finland
115
    "FR",  # France
116
    "GB",  # United Kingdom
117
    "GR",  # Greece
118
    "HR",  # Croatia
119
    "HU",  # Hungary
120
    "IE",  # Ireland
121
    "IT",  # Italy
122
    "LT",  # Lituania
123
    "LU",  # Luxembourg
124
    "LV",  # Latvia
125
    "MT",  # Malta
126
    "MY",  # Malaysia
127
    "NL",  # Netherlands
128
    "NZ",  # New Zealand
129
    "PL",  # Poland
130
    "PT",  # Portugal
131
    "RO",  # Romania
132
    "SE",  # Sweden
133
    "SG",  # Singapore
134
    "SI",  # Slovenia
135
    "SK",  # Slovakia
136
    "US",  # United States
137
]
138
relay_countries = set(get_args(CountryStr))
1✔
139

140
# Periodic subscription categories
141
PeriodStr = Literal["monthly", "yearly"]
1✔
142

143

144
# A Stripe Price, along with key details for Relay website
145
# https://stripe.com/docs/api/prices/object
146
class StripePriceDef(TypedDict):
1✔
147
    id: str  # Must start with "price_"
1✔
148
    price: float
1✔
149
    currency: CurrencyStr
1✔
150

151

152
PricesForPeriodDict = dict[PeriodStr, StripePriceDef]
1✔
153
LanguageOrAny = LanguageStr | Literal["*"]
1✔
154
PricePeriodsForLanguageDict = dict[LanguageOrAny, PricesForPeriodDict]
1✔
155
PlanCountryLangMapping = dict[CountryStr, PricePeriodsForLanguageDict]
1✔
156

157
#
158
# Public functions
159
#
160

161

162
def get_premium_country_language_mapping() -> PlanCountryLangMapping:
1✔
163
    """Get mapping for premium countries (unlimited masks, custom subdomain)"""
164
    return _country_language_mapping("premium")
1✔
165

166

167
def get_premium_countries() -> set[CountryStr]:
1✔
168
    """Get the country codes where Relay premium can be sold"""
169
    mapping = get_premium_country_language_mapping()
1✔
170
    return set(mapping.keys())
1✔
171

172

173
def get_phone_country_language_mapping() -> PlanCountryLangMapping:
1✔
174
    """Get mapping for phone countries (premium + phone mask)"""
175
    return _country_language_mapping("phones")
1✔
176

177

178
def get_bundle_country_language_mapping() -> PlanCountryLangMapping:
1✔
179
    """Get mapping for bundle countries (premium + phone mask + VPN)"""
180
    return _country_language_mapping("bundle")
1✔
181

182

183
#
184
# Private types for Selected Stripe data (_STRIPE_PLAN_DATA)
185
#
186

187
# RFC 5646 regional language tags handled by Relay
188
# Typically an ISO 639 language code, a dash, and an ISO 3166 country code
189
_RegionalLanguageStr = Literal[
1✔
190
    "de-CH",  # German (Swiss)
191
    "fr-CH",  # French (Swiss)
192
    "it-CH",  # Italian (Swiss)
193
]
194

195
# Stripe plans are associated with a country or country-language pair
196
_CountryOrRegion = CountryStr | _RegionalLanguageStr
1✔
197

198

199
# Types for _STRIPE_PLAN_DATA
200
class _StripeMonthlyPriceDetails(TypedDict):
1✔
201
    monthly: float
1✔
202
    monthly_when_yearly: float
1✔
203

204

205
class _StripeMonthlyCountryDetails(TypedDict):
1✔
206
    currency: CurrencyStr
1✔
207
    monthly_id: str
1✔
208
    yearly_id: str
1✔
209

210

211
class _StripeMonthlyPlanDetails(TypedDict):
1✔
212
    periods: Literal["monthly_and_yearly"]
1✔
213
    prices: dict[CurrencyStr, _StripeMonthlyPriceDetails]
1✔
214
    countries_and_regions: dict[_CountryOrRegion, _StripeMonthlyCountryDetails]
1✔
215

216

217
class _StripeYearlyPriceDetails(TypedDict):
1✔
218
    monthly_when_yearly: float
1✔
219

220

221
class _StripeYearlyCountryDetails(TypedDict):
1✔
222
    currency: CurrencyStr
1✔
223
    yearly_id: str
1✔
224

225

226
class _StripeYearlyPlanDetails(TypedDict):
1✔
227
    periods: Literal["yearly"]
1✔
228
    prices: dict[CurrencyStr, _StripeYearlyPriceDetails]
1✔
229
    countries_and_regions: dict[_CountryOrRegion, _StripeYearlyCountryDetails]
1✔
230

231

232
class _StripePlanData(TypedDict):
1✔
233
    premium: _StripeMonthlyPlanDetails
1✔
234
    phones: _StripeMonthlyPlanDetails
1✔
235
    bundle: _StripeYearlyPlanDetails
1✔
236

237

238
_StripePlanDetails = _StripeMonthlyPlanDetails | _StripeYearlyPlanDetails
1✔
239

240
# Selected Stripe data
241
# The "source of truth" is the Stripe data, this copy is used for upsell views
242
# and directing users to the correct Stripe purchase page.
243
_STRIPE_PLAN_DATA: _StripePlanData = {
1✔
244
    "premium": {
245
        "periods": "monthly_and_yearly",
246
        "prices": {
247
            "CHF": {"monthly": 2.00, "monthly_when_yearly": 1.00},
248
            "CZK": {"monthly": 47.0, "monthly_when_yearly": 23.0},
249
            "DKK": {"monthly": 15.0, "monthly_when_yearly": 7.00},
250
            "EUR": {"monthly": 1.99, "monthly_when_yearly": 0.99},
251
            "PLN": {"monthly": 8.00, "monthly_when_yearly": 5.00},
252
            "USD": {"monthly": 1.99, "monthly_when_yearly": 0.99},
253
        },
254
        "countries_and_regions": {
255
            "de-CH": {  # German-speaking Switzerland
256
                "currency": "CHF",
257
                "monthly_id": "price_1LYCqOJNcmPzuWtRuIXpQRxi",
258
                "yearly_id": "price_1LYCqyJNcmPzuWtR3Um5qDPu",
259
            },
260
            "fr-CH": {  # French-speaking Switzerland
261
                "currency": "CHF",
262
                "monthly_id": "price_1LYCvpJNcmPzuWtRq9ci2gXi",
263
                "yearly_id": "price_1LYCwMJNcmPzuWtRm6ebmq2N",
264
            },
265
            "it-CH": {  # Italian-speaking Switzerland
266
                "currency": "CHF",
267
                "monthly_id": "price_1LYCiBJNcmPzuWtRxtI8D5Uy",
268
                "yearly_id": "price_1LYClxJNcmPzuWtRWjslDdkG",
269
            },
270
            "BG": {  # Bulgaria
271
                "currency": "EUR",
272
                "monthly_id": "price_1NOSjBJNcmPzuWtRMQwYp5u1",
273
                "yearly_id": "price_1NOSkTJNcmPzuWtRpbKwsLcw",
274
            },
275
            "CY": {  # Cyprus
276
                "currency": "EUR",
277
                "monthly_id": "price_1NH9saJNcmPzuWtRpffF5I59",
278
                "yearly_id": "price_1NH9rKJNcmPzuWtRzDiXCeEG",
279
            },
280
            "CZ": {  # Czech Republic / Czechia
281
                "currency": "CZK",
282
                "monthly_id": "price_1NNkAlJNcmPzuWtRxsfrXacj",
283
                "yearly_id": "price_1NNkDHJNcmPzuWtRHnQmCDGP",
284
            },
285
            "DE": {  # Germany
286
                "currency": "EUR",
287
                "monthly_id": "price_1LYC79JNcmPzuWtRU7Q238yL",
288
                "yearly_id": "price_1LYC7xJNcmPzuWtRcdKXCVZp",
289
            },
290
            "DK": {  # Denmark
291
                "currency": "DKK",
292
                "monthly_id": "price_1NNfPCJNcmPzuWtR3SNA8gqG",
293
                "yearly_id": "price_1NNfLoJNcmPzuWtRpmLc9lst",
294
            },
295
            "EE": {  # Estonia
296
                "currency": "EUR",
297
                "monthly_id": "price_1NHA1tJNcmPzuWtRvSeyiVYH",
298
                "yearly_id": "price_1NHA2TJNcmPzuWtR10yknZHf",
299
            },
300
            "ES": {  # Spain
301
                "currency": "EUR",
302
                "monthly_id": "price_1LYCWmJNcmPzuWtRtopZog9E",
303
                "yearly_id": "price_1LYCXNJNcmPzuWtRu586XOFf",
304
            },
305
            "FI": {  # Finland
306
                "currency": "EUR",
307
                "monthly_id": "price_1LYBn9JNcmPzuWtRI3nvHgMi",
308
                "yearly_id": "price_1LYBq1JNcmPzuWtRmyEa08Wv",
309
            },
310
            "FR": {  # France
311
                "currency": "EUR",
312
                "monthly_id": "price_1LYBuLJNcmPzuWtRn58XQcky",
313
                "yearly_id": "price_1LYBwcJNcmPzuWtRpgoWcb03",
314
            },
315
            "GB": {  # United Kingdom
316
                "currency": "USD",
317
                "monthly_id": "price_1LYCHpJNcmPzuWtRhrhSYOKB",
318
                "yearly_id": "price_1LYCIlJNcmPzuWtRQtYLA92j",
319
            },
320
            "GR": {  # Greece
321
                "currency": "EUR",
322
                "monthly_id": "price_1NHA5CJNcmPzuWtR1JSmxqFA",
323
                "yearly_id": "price_1NHA4lJNcmPzuWtRniS23IuE",
324
            },
325
            "HR": {  # Croatia
326
                "currency": "EUR",
327
                "monthly_id": "price_1NOSznJNcmPzuWtRH7CEeAwA",
328
                "yearly_id": "price_1NOT0WJNcmPzuWtRpeNDEjvC",
329
            },
330
            "HU": {  # Hungary
331
                "currency": "EUR",
332
                "monthly_id": "price_1NOOJAJNcmPzuWtRV7Kmwmdm",
333
                "yearly_id": "price_1NOOKvJNcmPzuWtR2DEWIRE4",
334
            },
335
            "IE": {  # Ireland
336
                "currency": "EUR",
337
                "monthly_id": "price_1LhdrkJNcmPzuWtRvCc4hsI2",
338
                "yearly_id": "price_1LhdprJNcmPzuWtR7HqzkXTS",
339
            },
340
            "IT": {  # Italy
341
                "currency": "EUR",
342
                "monthly_id": "price_1LYCMrJNcmPzuWtRTP9vD8wY",
343
                "yearly_id": "price_1LYCN2JNcmPzuWtRtWz7yMno",
344
            },
345
            "LT": {  # Lithuania
346
                "currency": "EUR",
347
                "monthly_id": "price_1NHACcJNcmPzuWtR5ZJeVtJA",
348
                "yearly_id": "price_1NHADOJNcmPzuWtR2PSMBMLr",
349
            },
350
            "LU": {  # Luxembourg
351
                "currency": "EUR",
352
                "monthly_id": "price_1NHAFZJNcmPzuWtRm5A7w5qJ",
353
                "yearly_id": "price_1NHAF8JNcmPzuWtRG1FiPK0N",
354
            },
355
            "LV": {  # Latvia
356
                "currency": "EUR",
357
                "monthly_id": "price_1NHAASJNcmPzuWtRpcliwx0R",
358
                "yearly_id": "price_1NHA9lJNcmPzuWtRLf7DV6GA",
359
            },
360
            "MT": {  # Malta
361
                "currency": "EUR",
362
                "monthly_id": "price_1NH9yxJNcmPzuWtRChanpIQU",
363
                "yearly_id": "price_1NH9y3JNcmPzuWtRIJkQos9q",
364
            },
365
            "NL": {  # Netherlands
366
                "currency": "EUR",
367
                "monthly_id": "price_1LYCdLJNcmPzuWtR0J1EHoJ0",
368
                "yearly_id": "price_1LYCdtJNcmPzuWtRVm4jLzq2",
369
            },
370
            "PL": {  # Poland
371
                "currency": "PLN",
372
                "monthly_id": "price_1NNKGJJNcmPzuWtRTlP7GKWW",
373
                "yearly_id": "price_1NNfCvJNcmPzuWtRCvFppHqt",
374
            },
375
            "PT": {  # Portugal
376
                "currency": "EUR",
377
                "monthly_id": "price_1NHAI1JNcmPzuWtRx8jXjkrQ",
378
                "yearly_id": "price_1NHAHWJNcmPzuWtRCRMnWyvK",
379
            },
380
            "RO": {  # Romania
381
                "currency": "EUR",
382
                "monthly_id": "price_1NOOEnJNcmPzuWtRicUvOyUy",
383
                "yearly_id": "price_1NOOEJJNcmPzuWtRyHqMe2jb",
384
            },
385
            "SE": {  # Sweden
386
                "currency": "EUR",
387
                "monthly_id": "price_1LYBblJNcmPzuWtRGRHIoYZ5",
388
                "yearly_id": "price_1LYBeMJNcmPzuWtRT5A931WH",
389
            },
390
            "SI": {  # Slovenia
391
                "currency": "EUR",
392
                "monthly_id": "price_1NHALmJNcmPzuWtR2nIoAzEt",
393
                "yearly_id": "price_1NHAL9JNcmPzuWtRSZ3BWQs0",
394
            },
395
            "SK": {  # Slovakia
396
                "currency": "EUR",
397
                "monthly_id": "price_1NHAJsJNcmPzuWtR71WX0Pz9",
398
                "yearly_id": "price_1NHAKYJNcmPzuWtRtETl30gb",
399
            },
400
            "US": {  # United States
401
                "currency": "USD",
402
                "monthly_id": "price_1LXUcnJNcmPzuWtRpbNOajYS",
403
                "yearly_id": "price_1LXUdlJNcmPzuWtRKTYg7mpZ",
404
            },
405
        },
406
    },
407
    "phones": {
408
        "periods": "monthly_and_yearly",
409
        "prices": {
410
            "USD": {"monthly": 4.99, "monthly_when_yearly": 3.99},
411
        },
412
        "countries_and_regions": {
413
            "US": {  # United States
414
                "currency": "USD",
415
                "monthly_id": "price_1Li0w8JNcmPzuWtR2rGU80P3",
416
                "yearly_id": "price_1Li15WJNcmPzuWtRIh0F4VwP",
417
            }
418
        },
419
    },
420
    "bundle": {
421
        "periods": "yearly",
422
        "prices": {
423
            "USD": {"monthly_when_yearly": 6.99},
424
        },
425
        "countries_and_regions": {
426
            "US": {  # United States
427
                "currency": "USD",
428
                "yearly_id": "price_1LwoSDJNcmPzuWtR6wPJZeoh",
429
            }
430
        },
431
    },
432
}
433

434

435
# Private types for _RELAY_PLANS
436
_RelayPlanCategory = Literal["premium", "phones", "bundle"]
1✔
437

438

439
class _RelayPlansByType(TypedDict, total=False):
1✔
440
    by_country_and_lang: dict[CountryStr, dict[LanguageStr, _CountryOrRegion]]
1✔
441
    by_country_override: dict[CountryStr, CountryStr]
1✔
442
    by_country: list[CountryStr]
1✔
443

444

445
_RelayPlans = dict[_RelayPlanCategory, _RelayPlansByType]
1✔
446

447

448
# Map of Relay-supported countries to languages and their plans
449
# The top-level key is the plan type, "premium" or "phones" or "bundle"
450
# The second-level key is a map from criteria to the Stripe plan country index:
451
#   - "by_country": The plan is indexed by the original country code.
452
#   - "by_country_override": The plan is indexed by a different country code.
453
#     For example, the "phones" plan in Canada ("CA") is the same as the United
454
#     States ("US") plan.
455
#   - "by_country_and_lang": The plan is indexed by country and language. For
456
#     example, German speakers in Belgium have a different plan ("DE") than Dutch
457
#     speakers ("NL"). The first language has the default plan index if none match.
458
# The different maps are used to find the CountryStr that is an index into the
459
# _STRIPE_PLAN_DATA for that plan type.
460
_RELAY_PLANS: _RelayPlans = {
1✔
461
    "premium": {
462
        "by_country": [
463
            "BG",  # Bulgaria
464
            "CY",  # Cyprus
465
            "CZ",  # Czech Republic / Czechia
466
            "DE",  # Germany
467
            "DK",  # Denmark
468
            "EE",  # Estonia
469
            "ES",  # Spain
470
            "FI",  # Finland
471
            "FR",  # France
472
            "GB",  # United Kingdom
473
            "GR",  # Greece
474
            "HR",  # Croatia
475
            "HU",  # Hungary
476
            "IE",  # Ireland
477
            "IT",  # Italy
478
            "LT",  # Lithuania
479
            "LU",  # Luxembourg
480
            "LV",  # Latvia
481
            "MT",  # Malta
482
            "NL",  # Netherlands
483
            "PL",  # Poland
484
            "PT",  # Portugal
485
            "RO",  # Romania
486
            "SE",  # Sweden
487
            "SI",  # Slovenia
488
            "SK",  # Slovakia
489
            "US",  # United States
490
        ],
491
        "by_country_override": {
492
            "AT": "DE",  # Austria -> Germany
493
            "CA": "US",  # Canada -> United States
494
            "MY": "GB",  # Malaysia -> United Kingdom
495
            "NZ": "GB",  # New Zealand -> United Kingdom
496
            "SG": "GB",  # Singapore -> United Kingdom
497
        },
498
        "by_country_and_lang": {
499
            "BE": {  # Belgium
500
                "fr": "FR",  # French-speaking Belgium -> France
501
                "de": "DE",  # German-speaking Belgium -> Germany
502
                "nl": "NL",  # Dutch-speaking Belgium -> Netherlands
503
            },
504
            "CH": {  # Switzerland
505
                "fr": "fr-CH",  # French-speaking Swiss
506
                "de": "de-CH",  # Germany-speaking Swiss
507
                "it": "it-CH",  # Italian-speaking Swiss
508
            },
509
        },
510
    },
511
    "phones": {
512
        "by_country": ["US"],  # United States
513
        "by_country_override": {"CA": "US"},  # Canada -> United States
514
    },
515
    "bundle": {
516
        "by_country": ["US"],  # United States
517
        "by_country_override": {"CA": "US"},  # Canada -> United States
518
    },
519
}
520

521

522
#
523
# Private functions
524
#
525

526

527
def _country_language_mapping(plan: _RelayPlanCategory) -> PlanCountryLangMapping:
1✔
528
    """Get plan mapping with cache parameters"""
529
    return _cached_country_language_mapping(
1✔
530
        plan=plan,
531
        us_premium_monthly_price_id=settings.PREMIUM_PLAN_ID_US_MONTHLY,
532
        us_premium_yearly_price_id=settings.PREMIUM_PLAN_ID_US_YEARLY,
533
        us_phone_monthly_price_id=settings.PHONE_PLAN_ID_US_MONTHLY,
534
        us_phone_yearly_price_id=settings.PHONE_PLAN_ID_US_YEARLY,
535
        us_bundle_yearly_price_id=settings.BUNDLE_PLAN_ID_US,
536
    )
537

538

539
@lru_cache
1✔
540
def _cached_country_language_mapping(
1✔
541
    plan: _RelayPlanCategory,
542
    us_premium_monthly_price_id: str,
543
    us_premium_yearly_price_id: str,
544
    us_phone_monthly_price_id: str,
545
    us_phone_yearly_price_id: str,
546
    us_bundle_yearly_price_id: str,
547
) -> PlanCountryLangMapping:
548
    """Create the plan mapping with settings overrides"""
549
    relay_maps = _RELAY_PLANS[plan]
1✔
550
    stripe_data = _get_stripe_data_with_overrides(
1✔
551
        us_premium_monthly_price_id=us_premium_monthly_price_id,
552
        us_premium_yearly_price_id=us_premium_yearly_price_id,
553
        us_phone_monthly_price_id=us_phone_monthly_price_id,
554
        us_phone_yearly_price_id=us_phone_yearly_price_id,
555
        us_bundle_yearly_price_id=us_bundle_yearly_price_id,
556
    )[plan]
557

558
    mapping: PlanCountryLangMapping = {}
1✔
559
    for relay_country in relay_maps.get("by_country", []):
1✔
560
        if relay_country in mapping:
1!
NEW
561
            raise ValueError("relay_country should not be in mapping.")
×
562
        mapping[relay_country] = {"*": _get_stripe_prices(relay_country, stripe_data)}
1✔
563

564
    for relay_country, override in relay_maps.get("by_country_override", {}).items():
1✔
565
        if relay_country in mapping:
1!
NEW
566
            raise ValueError("relay_country should not be in mapping.")
×
567
        mapping[relay_country] = {"*": _get_stripe_prices(override, stripe_data)}
1✔
568

569
    for relay_country, languages in relay_maps.get("by_country_and_lang", {}).items():
1✔
570
        if relay_country in mapping:
1!
NEW
571
            raise ValueError("relay_country should not be in mapping.")
×
572
        mapping[relay_country] = {
1✔
573
            lang: _get_stripe_prices(stripe_country, stripe_data)
574
            for lang, stripe_country in languages.items()
575
        }
576
    # Sort by country code
577
    return {code: mapping[code] for code in sorted(mapping)}
1✔
578

579

580
def _get_stripe_prices(
1✔
581
    country_or_region: _CountryOrRegion, data: _StripePlanDetails
582
) -> PricesForPeriodDict:
583
    """Return the Stripe monthly and yearly price data for the given country."""
584
    stripe_details = data["countries_and_regions"][country_or_region]
1✔
585
    currency = stripe_details["currency"]
1✔
586
    prices = data["prices"][currency]
1✔
587
    period_to_details: PricesForPeriodDict = {}
1✔
588
    if data["periods"] == "monthly_and_yearly":
1✔
589
        # mypy thinks stripe_details _could_ be _StripeYearlyPriceDetails,
590
        # so extra asserts are needed to make mypy happy.
591
        monthly_id = str(stripe_details.get("monthly_id"))
1✔
592
        if not monthly_id.startswith("price_"):
1!
NEW
593
            raise ValueError("monthly_id must start with 'price_'")
×
594
        price = prices.get("monthly", 0.0)
1✔
595
        if not isinstance(price, float):
1!
NEW
596
            raise TypeError("price must be of type float.")
×
597
        period_to_details["monthly"] = {
1✔
598
            "id": monthly_id,
599
            "currency": currency,
600
            "price": price,
601
        }
602
    yearly_id = stripe_details["yearly_id"]
1✔
603
    if not yearly_id.startswith("price_"):
1!
NEW
604
        raise ValueError("yearly_id must start with 'price_'")
×
605
    period_to_details["yearly"] = {
1✔
606
        "id": yearly_id,
607
        "currency": currency,
608
        "price": prices["monthly_when_yearly"],
609
    }
610
    return period_to_details
1✔
611

612

613
@lru_cache
1✔
614
def _get_stripe_data_with_overrides(
1✔
615
    us_premium_monthly_price_id: str,
616
    us_premium_yearly_price_id: str,
617
    us_phone_monthly_price_id: str,
618
    us_phone_yearly_price_id: str,
619
    us_bundle_yearly_price_id: str,
620
) -> _StripePlanData:
621
    """Returns the Stripe plan data with settings overrides"""
622
    plan_data = deepcopy(_STRIPE_PLAN_DATA)
1✔
623
    plan_data["premium"]["countries_and_regions"]["US"][
1✔
624
        "monthly_id"
625
    ] = us_premium_monthly_price_id
626
    plan_data["premium"]["countries_and_regions"]["US"][
1✔
627
        "yearly_id"
628
    ] = us_premium_yearly_price_id
629
    plan_data["phones"]["countries_and_regions"]["US"][
1✔
630
        "monthly_id"
631
    ] = us_phone_monthly_price_id
632
    plan_data["phones"]["countries_and_regions"]["US"][
1✔
633
        "yearly_id"
634
    ] = us_phone_yearly_price_id
635
    plan_data["bundle"]["countries_and_regions"]["US"][
1✔
636
        "yearly_id"
637
    ] = us_bundle_yearly_price_id
638
    return plan_data
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc