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

mozilla / fx-private-relay / 58d1f4ed-3c30-4ee5-bcbb-84acf83362db

19 Mar 2025 01:25PM CUT coverage: 85.157% (+0.02%) from 85.137%
58d1f4ed-3c30-4ee5-bcbb-84acf83362db

Pull #5456

circleci

groovecoder
for MPP-4020: add sp3_plans to backend and API
Pull Request #5456: for MPP-4020: add sp3_plans to backend and API

2439 of 3569 branches covered (68.34%)

Branch coverage included in aggregate %.

49 of 50 new or added lines in 3 files covered. (98.0%)

1 existing line in 1 file now uncovered.

17136 of 19418 relevant lines covered (88.25%)

9.84 hits per line

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

97.78
/privaterelay/sp3_plans.py
1
from functools import lru_cache
1✔
2
from typing import Literal, TypedDict
1✔
3

4
from django.conf import settings
1✔
5
from django.http import HttpRequest
1✔
6

7
from privaterelay.utils import _get_cc_from_request
1✔
8

9
#
10
# Public types
11
#
12

13
CurrencyStr = Literal["CHF", "CZK", "DKK", "EUR", "PLN", "USD"]
1✔
14
LanguageStr = Literal["de", "fr", "it", "nl"]
1✔
15
CountryStr = Literal[
1✔
16
    "AT",
17
    "BE",
18
    "BG",
19
    "CA",
20
    "CH",
21
    "CY",
22
    "CZ",
23
    "DE",
24
    "DK",
25
    "EE",
26
    "ES",
27
    "FI",
28
    "FR",
29
    "GB",
30
    "GR",
31
    "HR",
32
    "HU",
33
    "IE",
34
    "IT",
35
    "LT",
36
    "LU",
37
    "LV",
38
    "MT",
39
    "MY",
40
    "NL",
41
    "NZ",
42
    "PL",
43
    "PR",
44
    "PT",
45
    "RO",
46
    "SE",
47
    "SG",
48
    "SI",
49
    "SK",
50
    "US",
51
]
52
PeriodStr = Literal["monthly", "yearly"]
1✔
53
PlanType = Literal["premium", "phones", "bundle"]
1✔
54
ProductKey = Literal[
1✔
55
    "relay-premium-127",
56
    "relay-premium-127-phone",
57
    "relay-email-phone-protection-127",
58
    "relay-premium-dev",
59
    "relay-email-phone-protection-dev",
60
    "bundle-relay-vpn-dev",
61
    "relaypremiumemailstage",
62
    "relaypremiumphonestage",
63
    "vpnrelaybundlestage",
64
]
65

66

67
class PlanPricing(TypedDict):
1✔
68
    monthly: dict[Literal["price", "currency", "url"], float | CurrencyStr | str]
1✔
69
    yearly: dict[Literal["price", "currency", "url"], float | CurrencyStr | str]
1✔
70

71

72
SP3PlanCountryLangMapping = dict[
1✔
73
    CountryStr, dict[LanguageStr | Literal["*"], PlanPricing]
74
]
75

76
#
77
# Pricing Data (simplified, no Stripe IDs)
78
#
79

80
PLAN_PRICING: dict[PlanType, dict[CurrencyStr, dict[PeriodStr, float]]] = {
1✔
81
    "premium": {
82
        "CHF": {"monthly": 2.00, "yearly": 1.00},
83
        "CZK": {"monthly": 47.0, "yearly": 23.0},
84
        "DKK": {"monthly": 15.0, "yearly": 7.00},
85
        "EUR": {"monthly": 1.99, "yearly": 0.99},
86
        "PLN": {"monthly": 8.00, "yearly": 5.00},
87
        "USD": {"monthly": 1.99, "yearly": 0.99},
88
    },
89
    "phones": {
90
        "USD": {"monthly": 4.99, "yearly": 3.99},
91
    },
92
    "bundle": {
93
        "USD": {"monthly": 6.99, "yearly": 6.99},
94
    },
95
}
96

97

98
#
99
# Public functions
100
#
101

102

103
def get_sp3_country_language_mapping(plan: PlanType) -> SP3PlanCountryLangMapping:
1✔
104
    """Get plan mapping for the given plan type."""
105
    return _cached_country_language_mapping(plan)
1✔
106

107

108
def get_supported_countries(plan: PlanType) -> set[CountryStr]:
1✔
109
    """Get the country codes where the plan is available."""
110
    return set(get_sp3_country_language_mapping(plan).keys())
1✔
111

112

113
def get_subscription_url(plan: PlanType, period: PeriodStr) -> str:
1✔
114
    """Generate the URL for a given plan and period."""
115
    product_key: ProductKey
116
    settings_attr = f"SUBPLAT3_{plan.upper()}_PRODUCT_KEY"
1✔
117
    product_key = getattr(settings, settings_attr)
1✔
118
    return f"{settings.SUBPLAT3_HOST}/{product_key}/{period}/landing"
1✔
119

120

121
def get_premium_countries() -> set[CountryStr]:
1✔
122
    """Return the merged set of premium, phones, and bundle country codes."""
NEW
123
    return (
×
124
        get_supported_countries("premium")
125
        | get_supported_countries("phones")
126
        | get_supported_countries("bundle")
127
    )
128

129

130
def is_plan_available_in_country(request: HttpRequest, plan: PlanType) -> bool:
1✔
131
    country_code = _get_cc_from_request(request)
1✔
132
    return country_code in get_supported_countries(plan)
1✔
133

134

135
#
136
# Internal caching
137
#
138
@lru_cache
1✔
139
def _cached_country_language_mapping(plan: PlanType) -> SP3PlanCountryLangMapping:
1✔
140
    """Create the plan mapping."""
141
    mapping: SP3PlanCountryLangMapping = {}
1✔
142

143
    for country in _get_supported_countries_by_plan(plan):
1✔
144
        currency = _get_country_currency(country)
1✔
145
        prices = PLAN_PRICING[plan].get(currency, {"monthly": 0.0, "yearly": 0.0})
1✔
146
        mapping[country] = {
1✔
147
            "*": {
148
                "monthly": {
149
                    "price": prices["monthly"],
150
                    "currency": currency,
151
                    "url": get_subscription_url(plan, "monthly"),
152
                },
153
                "yearly": {
154
                    "price": prices["yearly"],
155
                    "currency": currency,
156
                    "url": get_subscription_url(plan, "yearly"),
157
                },
158
            }
159
        }
160

161
    return mapping
1✔
162

163

164
def _get_supported_countries_by_plan(plan: PlanType) -> list[CountryStr]:
1✔
165
    """Return the list of supported countries for the given plan."""
166
    plan_countries: dict[PlanType, list[CountryStr]] = {
1✔
167
        "premium": [
168
            "AT",
169
            "BE",
170
            "BG",
171
            "CA",
172
            "CH",
173
            "CY",
174
            "CZ",
175
            "DE",
176
            "DK",
177
            "EE",
178
            "ES",
179
            "FI",
180
            "FR",
181
            "GB",
182
            "GR",
183
            "HR",
184
            "HU",
185
            "IE",
186
            "IT",
187
            "LT",
188
            "LU",
189
            "LV",
190
            "MT",
191
            "MY",
192
            "NL",
193
            "NZ",
194
            "PL",
195
            "PR",
196
            "PT",
197
            "RO",
198
            "SE",
199
            "SG",
200
            "SI",
201
            "SK",
202
            "US",
203
        ],
204
        "phones": ["US", "CA", "PR"],
205
        "bundle": ["US", "CA", "PR"],
206
    }
207
    return plan_countries.get(plan, [])
1✔
208

209

210
def _get_country_currency(country: CountryStr) -> CurrencyStr:
1✔
211
    """Return the default currency for a given country."""
212
    country_currency_map: dict[CountryStr, CurrencyStr] = {
1✔
213
        "AT": "EUR",
214
        "BE": "EUR",
215
        "BG": "EUR",
216
        "CA": "USD",
217
        "CH": "CHF",
218
        "CY": "EUR",
219
        "CZ": "CZK",
220
        "DE": "EUR",
221
        "DK": "DKK",
222
        "EE": "EUR",
223
        "ES": "EUR",
224
        "FI": "EUR",
225
        "FR": "EUR",
226
        "GB": "USD",
227
        "GR": "EUR",
228
        "HR": "EUR",
229
        "HU": "EUR",
230
        "IE": "EUR",
231
        "IT": "EUR",
232
        "LT": "EUR",
233
        "LU": "EUR",
234
        "LV": "EUR",
235
        "MT": "EUR",
236
        "MY": "USD",
237
        "NL": "EUR",
238
        "NZ": "USD",
239
        "PL": "PLN",
240
        "PR": "USD",
241
        "PT": "EUR",
242
        "RO": "EUR",
243
        "SE": "EUR",
244
        "SG": "USD",
245
        "SI": "EUR",
246
        "SK": "EUR",
247
        "US": "USD",
248
    }
249
    return country_currency_map.get(country, "USD")
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