-
Notifications
You must be signed in to change notification settings - Fork 629
/
PaymentIntent.kt
412 lines (350 loc) · 14.1 KB
/
PaymentIntent.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
package com.stripe.android.model
import androidx.annotation.RestrictTo
import com.stripe.android.core.model.StripeJsonUtils
import com.stripe.android.core.model.StripeModel
import com.stripe.android.model.PaymentIntent.CaptureMethod
import com.stripe.android.model.PaymentIntent.ConfirmationMethod
import com.stripe.android.model.parsers.PaymentIntentJsonParser
import kotlinx.parcelize.Parcelize
import org.json.JSONObject
import java.util.regex.Pattern
/**
* A [PaymentIntent] tracks the process of collecting a payment from your customer.
*
* - [Payment Intents Overview](https://stripe.com/docs/payments/payment-intents)
* - [PaymentIntents API Reference](https://stripe.com/docs/api/payment_intents)
*/
@Parcelize
data class PaymentIntent internal constructor(
/**
* Unique identifier for the object.
*/
override val id: String?,
/**
* The list of payment method types (e.g. card) that this [PaymentIntent] is allowed to
* use.
*/
override val paymentMethodTypes: List<String>,
/**
* Amount intended to be collected by this [PaymentIntent]. A positive integer
* representing how much to charge in the smallest currency unit (e.g., 100 cents to charge
* $1.00 or 100 to charge ¥100, a zero-decimal currency). The minimum amount is $0.50 US or
* equivalent in charge currency. The amount value supports up to eight digits (e.g., a value
* of 99999999 for a USD charge of $999,999.99).
*/
val amount: Long?,
/**
* Populated when `status` is `canceled`, this is the time at which the [PaymentIntent]
* was canceled. Measured in seconds since the Unix epoch. If unavailable, will return 0.
*/
val canceledAt: Long = 0L,
/**
* Reason for cancellation of this [PaymentIntent].
*/
val cancellationReason: CancellationReason? = null,
/**
* Controls when the funds will be captured from the customer’s account.
* See [CaptureMethod].
*/
val captureMethod: CaptureMethod = CaptureMethod.Automatic,
/**
* The client secret of this [PaymentIntent]. Used for client-side retrieval using a
* publishable key.
*
* The client secret can be used to complete a payment from your frontend.
* It should not be stored, logged, embedded in URLs, or exposed to anyone other than the
* customer. Make sure that you have TLS enabled on any page that includes the client
* secret.
*/
override val clientSecret: String?,
/**
* One of automatic (default) or manual. See [ConfirmationMethod].
*
* When [confirmationMethod] is `automatic`, a [PaymentIntent] can be confirmed
* using a publishable key. After `next_action`s are handled, no additional
* confirmation is required to complete the payment.
*
* When [confirmationMethod] is `manual`, all payment attempts must be made
* using a secret key. The [PaymentIntent] returns to the
* [RequiresConfirmation][StripeIntent.Status.RequiresConfirmation]
* state after handling `next_action`s, and requires your server to initiate each
* payment attempt with an explicit confirmation.
*/
val confirmationMethod: ConfirmationMethod = ConfirmationMethod.Automatic,
/**
* Country code of the user.
*/
override val countryCode: String?,
/**
* Time at which the object was created. Measured in seconds since the Unix epoch.
*/
override val created: Long,
/**
* Three-letter ISO currency code, in lowercase. Must be a supported currency.
*/
val currency: String?,
/**
* An arbitrary string attached to the object. Often useful for displaying to users.
*/
override val description: String? = null,
/**
* Has the value `true` if the object exists in live mode or the value
* `false` if the object exists in test mode.
*/
override val isLiveMode: Boolean,
override val paymentMethod: PaymentMethod? = null,
/**
* ID of the payment method (a PaymentMethod, Card, BankAccount, or saved Source object)
* to attach to this [PaymentIntent].
*/
override val paymentMethodId: String? = null,
/**
* Email address that the receipt for the resulting payment will be sent to.
*/
val receiptEmail: String? = null,
/**
* Status of this [PaymentIntent].
*/
override val status: StripeIntent.Status? = null,
val setupFutureUsage: StripeIntent.Usage? = null,
/**
* The payment error encountered in the previous [PaymentIntent] confirmation.
*/
val lastPaymentError: Error? = null,
/**
* Shipping information for this [PaymentIntent].
*/
val shipping: Shipping? = null,
/**
* Payment types that have not been activated in livemode, but have been activated in testmode.
*/
override val unactivatedPaymentMethods: List<String>,
/**
* Payment types that are accepted when paying with Link.
*/
override val linkFundingSources: List<String> = emptyList(),
override val nextActionData: StripeIntent.NextActionData? = null,
private val paymentMethodOptionsJsonString: String? = null
) : StripeIntent {
fun getPaymentMethodOptions() = paymentMethodOptionsJsonString?.let {
StripeJsonUtils.jsonObjectToMap(JSONObject(it))
} ?: emptyMap()
override val nextActionType: StripeIntent.NextActionType?
get() = when (nextActionData) {
is StripeIntent.NextActionData.SdkData -> StripeIntent.NextActionType.UseStripeSdk
is StripeIntent.NextActionData.RedirectToUrl -> StripeIntent.NextActionType.RedirectToUrl
is StripeIntent.NextActionData.DisplayOxxoDetails -> StripeIntent.NextActionType.DisplayOxxoDetails
is StripeIntent.NextActionData.VerifyWithMicrodeposits ->
StripeIntent.NextActionType.VerifyWithMicrodeposits
else -> null
}
override val isConfirmed: Boolean
get() = setOf(
StripeIntent.Status.Processing,
StripeIntent.Status.RequiresCapture,
StripeIntent.Status.Succeeded
).contains(status)
override val lastErrorMessage: String?
get() = lastPaymentError?.message
override fun requiresAction(): Boolean {
return status === StripeIntent.Status.RequiresAction
}
override fun requiresConfirmation(): Boolean {
return status === StripeIntent.Status.RequiresConfirmation
}
/**
* SetupFutureUsage is considered to be set if it is on or off session.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
private fun isTopLevelSetupFutureUsageSet() =
when (setupFutureUsage) {
StripeIntent.Usage.OnSession -> true
StripeIntent.Usage.OffSession -> true
StripeIntent.Usage.OneTime -> false
null -> false
}
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun isLpmLevelSetupFutureUsageSet(code: PaymentMethodCode): Boolean {
return isTopLevelSetupFutureUsageSet() || paymentMethodOptionsJsonString?.let {
JSONObject(paymentMethodOptionsJsonString)
.optJSONObject(code)
?.optString("setup_future_usage")?.let {
true
} ?: false
} ?: false
}
/**
* The payment error encountered in the previous [PaymentIntent] confirmation.
*
* See [last_payment_error](https://stripe.com/docs/api/payment_intents/object#payment_intent_object-last_payment_error).
*/
@Parcelize
data class Error internal constructor(
/**
* For card errors, the ID of the failed charge.
*/
val charge: String?,
/**
* For some errors that could be handled programmatically, a short string indicating the
* [error code](https://stripe.com/docs/error-codes) reported.
*/
val code: String?,
/**
* For card errors resulting from a card issuer decline, a short string indicating the
* [card issuer’s reason for the decline](https://stripe.com/docs/declines#issuer-declines)
* if they provide one.
*/
val declineCode: String?,
/**
* A URL to more information about the
* [error code](https://stripe.com/docs/error-codes) reported.
*/
val docUrl: String?,
/**
* A human-readable message providing more details about the error. For card errors,
* these messages can be shown to your users.
*/
val message: String?,
/**
* If the error is parameter-specific, the parameter related to the error.
* For example, you can use this to display a message near the correct form field.
*/
val param: String?,
/**
* The PaymentMethod object for errors returned on a request involving a PaymentMethod.
*/
val paymentMethod: PaymentMethod?,
/**
* The type of error returned.
*/
val type: Type?
) : StripeModel {
enum class Type(val code: String) {
ApiConnectionError("api_connection_error"),
ApiError("api_error"),
AuthenticationError("authentication_error"),
CardError("card_error"),
IdempotencyError("idempotency_error"),
InvalidRequestError("invalid_request_error"),
RateLimitError("rate_limit_error");
internal companion object {
fun fromCode(typeCode: String?) = values().firstOrNull { it.code == typeCode }
}
}
internal companion object {
internal const val CODE_AUTHENTICATION_ERROR = "payment_intent_authentication_failure"
}
}
/**
* Shipping information for this [PaymentIntent].
*
* See [shipping](https://stripe.com/docs/api/payment_intents/object#payment_intent_object-shipping)
*/
@Parcelize
data class Shipping(
/**
* Shipping address.
*
* See [shipping.address](https://stripe.com/docs/api/payment_intents/object#payment_intent_object-shipping-address)
*/
val address: Address,
/**
* The delivery service that shipped a physical product, such as Fedex, UPS, USPS, etc.
*
* See [shipping.carrier](https://stripe.com/docs/api/payment_intents/object#payment_intent_object-shipping-carrier)
*/
val carrier: String? = null,
/**
* Recipient name.
*
* See [shipping.name](https://stripe.com/docs/api/payment_intents/object#payment_intent_object-shipping-name)
*/
val name: String? = null,
/**
* Recipient phone (including extension).
*
* See [shipping.phone](https://stripe.com/docs/api/payment_intents/object#payment_intent_object-shipping-phone)
*/
val phone: String? = null,
/**
* The tracking number for a physical product, obtained from the delivery service.
* If multiple tracking numbers were generated for this purchase, please separate them
* with commas.
*
* See [shipping.tracking_number](https://stripe.com/docs/api/payment_intents/object#payment_intent_object-shipping-tracking_number)
*/
val trackingNumber: String? = null
) : StripeModel
internal data class ClientSecret(internal val value: String) {
internal val paymentIntentId: String =
value.split("_secret".toRegex())
.dropLastWhile { it.isEmpty() }.toTypedArray()[0]
init {
require(isMatch(value)) {
"Invalid Payment Intent client secret: $value"
}
}
internal companion object {
private val PATTERN = Pattern.compile("^pi_[^_]+_secret_[^_]+$")
fun isMatch(value: String) = PATTERN.matcher(value).matches()
}
}
/**
* Reason for cancellation of this [PaymentIntent], either user-provided (duplicate, fraudulent,
* requested_by_customer, or abandoned) or generated by Stripe internally (failed_invoice,
* void_invoice, or automatic).
*/
enum class CancellationReason(private val code: String) {
Duplicate("duplicate"),
Fraudulent("fraudulent"),
RequestedByCustomer("requested_by_customer"),
Abandoned("abandoned"),
FailedInvoice("failed_invoice"),
VoidInvoice("void_invoice"),
Automatic("automatic");
internal companion object {
fun fromCode(code: String?) = values().firstOrNull { it.code == code }
}
}
/**
* Controls when the funds will be captured from the customer’s account.
*/
enum class CaptureMethod(private val code: String) {
/**
* (Default) Stripe automatically captures funds when the customer authorizes the payment.
*/
Automatic("automatic"),
/**
* Place a hold on the funds when the customer authorizes the payment, but don’t capture
* the funds until later. (Not all payment methods support this.)
*/
Manual("manual");
internal companion object {
fun fromCode(code: String?) = values().firstOrNull { it.code == code } ?: Automatic
}
}
enum class ConfirmationMethod(private val code: String) {
/**
* (Default) PaymentIntent can be confirmed using a publishable key. After `next_action`s
* are handled, no additional confirmation is required to complete the payment.
*/
Automatic("automatic"),
/**
* All payment attempts must be made using a secret key. The PaymentIntent returns to the
* `requires_confirmation` state after handling `next_action`s, and requires your server to
* initiate each payment attempt with an explicit confirmation.
*/
Manual("manual");
internal companion object {
fun fromCode(code: String?) = values().firstOrNull { it.code == code } ?: Automatic
}
}
companion object {
@JvmStatic
fun fromJson(jsonObject: JSONObject?): PaymentIntent? {
return jsonObject?.let {
PaymentIntentJsonParser().parse(it)
}
}
}
}