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}