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}