Skip to main content

stripe_pay_client/element/
impl.rs

1use super::*;
2
3impl ElementConfig {
4    /// Build a configuration for a client secret.
5    ///
6    /// # Arguments
7    ///
8    /// - `String` - the client secret Stripe issued for the intent.
9    ///
10    /// # Returns
11    ///
12    /// - `Self` - the configuration, defaulting to the payment variant.
13    pub fn new(client_secret: String) -> Self {
14        Self {
15            client_secret,
16            kind: ElementKind::Payment,
17            locale: String::new(),
18        }
19    }
20
21    /// Return a copy of this configuration rendering the given variant.
22    ///
23    /// # Arguments
24    ///
25    /// - `ElementKind` - the variant the page should mount.
26    ///
27    /// # Returns
28    ///
29    /// - `Self` - the configuration with the variant switched.
30    pub fn with_kind(mut self, kind: ElementKind) -> Self {
31        self.set_kind(kind);
32        self
33    }
34
35    /// Return a copy of this configuration with an explicit locale.
36    ///
37    /// # Arguments
38    ///
39    /// - `String` - the locale tag, such as `en` or `zh-CN`.
40    ///
41    /// # Returns
42    ///
43    /// - `Self` - the configuration with the locale set.
44    pub fn with_locale(mut self, locale: String) -> Self {
45        self.set_locale(locale);
46        self
47    }
48
49    /// Return the locale actually sent to Stripe.js.
50    ///
51    /// An empty locale means the host page did not choose one, so the
52    /// element falls back to the crate default rather than sending an
53    /// empty string Stripe would reject.
54    ///
55    /// # Returns
56    ///
57    /// - `&str` - the effective locale.
58    pub fn effective_locale(&self) -> &str {
59        let requested: &str = self.get_locale();
60        if requested.is_empty() {
61            DEFAULT_LOCALE
62        } else {
63            requested
64        }
65    }
66
67    /// Return whether the configuration can mount an element.
68    ///
69    /// Stripe rejects an empty client secret, so a blank one has to
70    /// fail before the element is ever created.
71    ///
72    /// # Returns
73    ///
74    /// - `bool` - `true` when a client secret is present.
75    pub fn is_mountable(&self) -> bool {
76        !self.get_client_secret().trim().is_empty()
77    }
78
79    /// Render the options object Stripe.js expects for an element.
80    ///
81    /// Stripe.js is called from JavaScript, so the crate passes a JSON
82    /// options object rather than typed arguments. Building it here
83    /// keeps the payload identical to what a hand-written mount would
84    /// send and lets the tests assert on it without a browser.
85    ///
86    /// # Returns
87    ///
88    /// - `Result<String, ElementError>` - the JSON options, or
89    ///   `MissingClientSecret` when the configuration is unusable.
90    pub fn build_options(&self) -> Result<String, ElementError> {
91        if !self.is_mountable() {
92            return Err(ElementError::MissingClientSecret);
93        }
94        let trimmed: String = format!(
95            "{{{}:{}{}{}}}",
96            CLIENT_SECRET_FIELD,
97            String::from("\""),
98            self.get_client_secret(),
99            String::from("\""),
100        );
101        Ok(format!(
102            "{},{}:{}\"{}\"{}}}",
103            trimmed.strip_suffix('}').unwrap_or(trimmed.as_str()),
104            LOCALE_FIELD,
105            String::from("\""),
106            self.effective_locale(),
107            String::from("\""),
108        ))
109    }
110
111    /// Return whether the page has loaded the Stripe.js global.
112    ///
113    /// The check is a pure boolean test against the global registry,
114    /// which keeps it callable from both wasm and the host target.
115    ///
116    /// # Arguments
117    ///
118    /// - `bool` - whether `window.Stripe` resolved when probed.
119    ///
120    /// # Returns
121    ///
122    /// - `Result<(), ElementError>` - `Ok(())` when Stripe.js is
123    ///   present, `StripeJsUnavailable` otherwise.
124    pub fn require_stripe_js(available: bool) -> Result<(), ElementError> {
125        if available {
126            return Ok(());
127        }
128        Err(ElementError::StripeJsUnavailable)
129    }
130
131    /// Check every precondition a mount needs, in the order a caller
132    /// hits them.
133    ///
134    /// # Arguments
135    ///
136    /// - `bool` - whether the page has a mount target.
137    /// - `bool` - whether Stripe.js is loaded.
138    ///
139    /// # Returns
140    ///
141    /// - `Result<(), ElementError>` - `Ok(())` when the element can
142    ///   mount, or the first unmet precondition.
143    pub fn preflight(
144        &self,
145        has_mount_target: bool,
146        stripe_js_loaded: bool,
147    ) -> Result<(), ElementError> {
148        if !self.is_mountable() {
149            return Err(ElementError::MissingClientSecret);
150        }
151        if !has_mount_target {
152            return Err(ElementError::MissingMountTarget);
153        }
154        Self::require_stripe_js(stripe_js_loaded)
155    }
156}