Skip to main content

stripe_pay_core/form/
impl.rs

1use super::*;
2
3impl FormField {
4    /// Build one bracketed parameter.
5    ///
6    /// # Arguments
7    ///
8    /// - `String` - the bracketed parameter name.
9    /// - `String` - the field's value.
10    ///
11    /// # Returns
12    ///
13    /// - `Self` - the field.
14    pub const fn new(key: String, value: String) -> Self {
15        Self { key, value }
16    }
17
18    /// Percent-encode a form key or value for Stripe's request body.
19    ///
20    /// Stripe expects `application/x-www-form-urlencoded`, which
21    /// encodes a space as `+` and every other reserved character as
22    /// `%XX` over uppercase hex digits. Encoding the bracketed key is
23    /// what turns `metadata[order_id]` into `metadata%5Border_id%5D`.
24    ///
25    /// # Arguments
26    ///
27    /// - `&str` - the raw key or value to escape.
28    ///
29    /// # Returns
30    ///
31    /// - `String` - the percent-encoded text.
32    pub fn percent_encode(raw: &str) -> String {
33        let mut encoded: String = String::with_capacity(raw.len());
34        for byte in raw.as_bytes() {
35            let byte: u8 = *byte;
36            if Self::is_unreserved(byte) {
37                encoded.push(char::from(byte));
38                continue;
39            }
40            if byte == SPACE {
41                encoded.push(PLUS_SIGN);
42                continue;
43            }
44            let hex: String = format!("{:02X}", byte);
45            encoded.push('%');
46            encoded.push_str(hex.as_str());
47        }
48        encoded
49    }
50
51    /// Return whether a byte may appear in a form body unescaped.
52    ///
53    /// This is the RFC 3986 unreserved set plus the five characters
54    /// `application/x-www-form-urlencoded` also passes through:
55    /// alphanumerics and `-` `_` `.` `~`. Brackets deliberately fall
56    /// outside the set, which is what turns `metadata[order_id]` into
57    /// `metadata%5Border_id%5D`.
58    ///
59    /// # Arguments
60    ///
61    /// - `u8` - the byte to classify.
62    ///
63    /// # Returns
64    ///
65    /// - `bool` - `true` when the byte needs no percent-escaping.
66    pub fn is_unreserved(byte: u8) -> bool {
67        byte.is_ascii_alphanumeric()
68            || byte == UNRESERVED_DASH
69            || byte == UNRESERVED_UNDERSCORE
70            || byte == UNRESERVED_DOT
71            || byte == UNRESERVED_TILDE
72    }
73
74    /// Build the bracketed key Stripe uses for one metadata entry.
75    ///
76    /// # Arguments
77    ///
78    /// - `&str` - the bare metadata key such as `order_id`.
79    ///
80    /// # Returns
81    ///
82    /// - `String` - the full parameter name `metadata[order_id]`.
83    pub fn metadata_key(key: &str) -> String {
84        let mut full: String = String::with_capacity(METADATA_PREFIX.len() + key.len() + 2);
85        full.push_str(METADATA_PREFIX);
86        full.push(FORM_OPEN_BRACKET);
87        full.push_str(key);
88        full.push(FORM_CLOSE_BRACKET);
89        full
90    }
91
92    /// Build the bracketed key Stripe uses for one `expand[]` entry.
93    ///
94    /// Stripe takes repeated list parameters as `expand[0]`, `expand[1]`,
95    /// so a caller expanding two fields must get two distinct keys.
96    ///
97    /// # Arguments
98    ///
99    /// - `usize` - the field's position in the expand list.
100    ///
101    /// # Returns
102    ///
103    /// - `String` - the full parameter name `expand[0]`.
104    pub fn expand_key(index: usize) -> String {
105        let mut full: String = String::with_capacity(EXPAND_PREFIX.len() + 8);
106        full.push_str(EXPAND_PREFIX);
107        full.push(FORM_OPEN_BRACKET);
108        full.push_str(index.to_string().as_str());
109        full.push(FORM_CLOSE_BRACKET);
110        full
111    }
112}
113
114impl FormParams {
115    /// Build an empty parameter set.
116    ///
117    /// # Returns
118    ///
119    /// - `Self` - a builder with no fields.
120    pub const fn new() -> Self {
121        Self { fields: Vec::new() }
122    }
123
124    /// Return the fields in insertion order.
125    ///
126    /// `#[derive(Getter)]` cannot express this accessor: a `clone`
127    /// getter hands back an owned `Vec` whose borrow dies at the end
128    /// of the statement, and a `deref` getter breaks the matching
129    /// `GetterMut` signature. A slice is the only return type that lets
130    /// `encode` sort the fields in place.
131    ///
132    /// # Returns
133    ///
134    /// - `&[FormField]` - the fields, in insertion order.
135    pub fn get_fields(&self) -> &[FormField] {
136        &self.fields
137    }
138
139    /// Return a mutable view of the fields.
140    ///
141    /// Kept crate-private on purpose: the public `set` builder is the
142    /// only path that should add a field, because it is what preserves
143    /// the one-entry-per-key invariant.
144    ///
145    /// # Returns
146    ///
147    /// - `&mut Vec<FormField>` - the fields, for in-place maintenance.
148    fn get_mut_fields(&mut self) -> &mut Vec<FormField> {
149        &mut self.fields
150    }
151
152    /// Return the number of fields in the builder.
153    ///
154    /// # Returns
155    ///
156    /// - `usize` - the field count.
157    pub fn len(&self) -> usize {
158        self.get_fields().len()
159    }
160
161    /// Return whether the builder holds no fields.
162    ///
163    /// # Returns
164    ///
165    /// - `bool` - `true` when there is nothing to encode.
166    pub fn is_empty(&self) -> bool {
167        self.get_fields().is_empty()
168    }
169
170    /// Add a field, replacing any existing field with the same key.
171    ///
172    /// # Arguments
173    ///
174    /// - `String` - the bracketed parameter name.
175    /// - `String` - the field's value.
176    ///
177    /// # Returns
178    ///
179    /// - `&mut Self` - the builder with the field added or replaced.
180    pub fn set(&mut self, key: String, value: String) -> &mut Self {
181        let fields: &mut Vec<FormField> = self.get_mut_fields();
182        let existing: Option<usize> = fields
183            .iter()
184            .position(|field: &FormField| *field.get_key() == key);
185        match existing {
186            Some(index) => {
187                fields[index] = FormField::new(key, value);
188            }
189            None => {
190                fields.push(FormField::new(key, value));
191            }
192        }
193        self
194    }
195
196    /// Add a field and return the builder by value.
197    ///
198    /// # Arguments
199    ///
200    /// - `String` - the bracketed parameter name.
201    /// - `String` - the field's value.
202    ///
203    /// # Returns
204    ///
205    /// - `Self` - the builder with the field added or replaced.
206    pub fn with(mut self, key: String, value: String) -> Self {
207        self.set(key, value);
208        self
209    }
210
211    /// Add a nested metadata entry using Stripe's bracket notation.
212    ///
213    /// Stripe limits metadata keys to 40 characters and values to 500;
214    /// both are checked here so a bad key fails at build time rather
215    /// than as an opaque 400 from the API.
216    ///
217    /// # Arguments
218    ///
219    /// - `String` - the metadata key.
220    /// - `String` - the metadata value.
221    ///
222    /// # Returns
223    ///
224    /// - `Result<Self, StripeError>` - the builder, or the reason the
225    ///   key or value exceeded Stripe's documented limits.
226    pub fn with_metadata(mut self, key: String, value: String) -> Result<Self, StripeError> {
227        if key.is_empty() || key.chars().count() > METADATA_KEY_MAX_CHARS {
228            return Err(StripeError::Malformed(String::from(
229                METADATA_KEY_LIMIT_MESSAGE,
230            )));
231        }
232        if value.chars().count() > METADATA_VALUE_MAX_CHARS {
233            return Err(StripeError::Malformed(String::from(
234                METADATA_VALUE_LIMIT_MESSAGE,
235            )));
236        }
237        self.set(FormField::metadata_key(&key), value);
238        Ok(self)
239    }
240
241    /// Return the body as a percent-encoded `key=value` string.
242    ///
243    /// Keys are sorted so the same logical request always produces the
244    /// same body, which makes request signing and test fixtures
245    /// reproducible.
246    ///
247    /// # Returns
248    ///
249    /// - `String` - the encoded body.
250    pub fn encode(&self) -> String {
251        let mut sorted: Vec<&FormField> = self.get_fields().iter().collect();
252        sorted.sort_by(|left: &&FormField, right: &&FormField| left.get_key().cmp(right.get_key()));
253        let mut body: String = String::new();
254        for field in sorted {
255            if !body.is_empty() {
256                body.push(FORM_PAIR_SEPARATOR);
257            }
258            body.push_str(FormField::percent_encode(field.get_key()).as_str());
259            body.push(FORM_KEY_VALUE_SEPARATOR);
260            body.push_str(FormField::percent_encode(field.get_value()).as_str());
261        }
262        body
263    }
264
265    /// Build the request body for creating a PaymentIntent.
266    ///
267    /// Stripe creates a PaymentIntent with a flat form body; the amount
268    /// travels as a minor-unit integer and the currency as its ISO 4217
269    /// code, exactly as `Money` stores them.
270    ///
271    /// # Arguments
272    ///
273    /// - `Money` - the amount to capture.
274    /// - `Option<String>` - the customer to charge, or `None` for an
275    ///   intent that is not yet attached to a customer.
276    ///
277    /// # Returns
278    ///
279    /// - `String` - the encoded request body.
280    pub fn encode_create_payment_intent(amount: Money, customer: Option<String>) -> String {
281        let mut params: Self = Self::new();
282        params.set(String::from(FORM_AMOUNT), amount.get_amount().to_string());
283        params.set(
284            String::from(FORM_CURRENCY),
285            String::from(amount.currency_code()),
286        );
287        if let Some(customer_id) = customer {
288            params.set(String::from(FORM_CUSTOMER), customer_id);
289        }
290        params.encode()
291    }
292
293    /// Build the request body for refunding part or all of a charge.
294    ///
295    /// # Arguments
296    ///
297    /// - `&str` - the charge being refunded.
298    /// - `Option<Money>` - the amount to return, or `None` to refund
299    ///   the charge's full remaining balance.
300    /// - `RefundReason` - why the money is being returned.
301    ///
302    /// # Returns
303    ///
304    /// - `String` - the encoded request body.
305    pub fn encode_create_refund(
306        charge: &str,
307        amount: Option<Money>,
308        reason: RefundReason,
309    ) -> String {
310        let mut params: Self = Self::new();
311        params.set(String::from(FORM_CHARGE), String::from(charge));
312        if let Some(refund_amount) = amount {
313            params.set(
314                String::from(FORM_AMOUNT),
315                refund_amount.get_amount().to_string(),
316            );
317        }
318        params.set(String::from(FORM_REASON), String::from(reason.as_str()));
319        params.encode()
320    }
321
322    /// Build the request body for confirming a PaymentIntent.
323    ///
324    /// Confirmation names the payment method and may set the return URL
325    /// the browser lands on once 3-D Secure finishes.
326    ///
327    /// # Arguments
328    ///
329    /// - `&str` - the PaymentIntent to confirm.
330    /// - `&str` - the payment method to charge.
331    /// - `Option<&str>` - the browser return URL, or `None` when the
332    ///   payment method owes no redirect.
333    ///
334    /// # Returns
335    ///
336    /// - `String` - the encoded request body.
337    pub fn encode_confirm_payment_intent(
338        payment_intent: &str,
339        payment_method: &str,
340        return_url: Option<&str>,
341    ) -> String {
342        let mut params: Self = Self::new();
343        params.set(
344            String::from(FORM_PAYMENT_INTENT),
345            String::from(payment_intent),
346        );
347        params.set(
348            String::from(FORM_PAYMENT_METHOD),
349            String::from(payment_method),
350        );
351        params.set(String::from(FORM_CONFIRM), String::from(TRUE_LITERAL));
352        if let Some(url) = return_url {
353            params.set(String::from(FORM_RETURN_URL), String::from(url));
354        }
355        params.encode()
356    }
357
358    /// Build the request body for creating a Customer.
359    ///
360    /// Stripe accepts a customer's email and description as flat form
361    /// fields; both are optional.
362    ///
363    /// # Arguments
364    ///
365    /// - `Option<&str>` - the customer's email, or `None` to omit it.
366    /// - `Option<&str>` - an internal description, or `None` to omit it.
367    ///
368    /// # Returns
369    ///
370    /// - `String` - the encoded request body.
371    pub fn encode_create_customer(email: Option<&str>, description: Option<&str>) -> String {
372        let mut params: Self = Self::new();
373        if let Some(address) = email {
374            params.set(String::from(FORM_EMAIL), String::from(address));
375        }
376        if let Some(text) = description {
377            params.set(String::from(FORM_DESCRIPTION), String::from(text));
378        }
379        params.encode()
380    }
381}