Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Commit

Permalink
Issue #9838: Introduce CreditCardValidationDelegate and implement onC…
Browse files Browse the repository at this point in the history
…reditCardSave in GeckoCreditCardsAddressesStorageDelegate

- Introduces `CreditCardValidationDelegate` and a default implementation in `DefaultCreditCardValidationDelegate`
- Adds `wipeLocalAddresses()` and `wipeLocalCreditCards()` to `CreditCardsAddressesStorage` to clear out local state in between tests
- Implements `onCreditCardSave` in `GeckoCreditCardsAddressesStorageDelegate`
  • Loading branch information
gabrielluong committed Apr 15, 2021
1 parent 5b3677a commit 719b601
Show file tree
Hide file tree
Showing 8 changed files with 426 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package mozilla.components.concept.storage

import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import kotlinx.coroutines.Deferred

/**
* An interface which defines read/write methods for credit card and address data.
Expand Down Expand Up @@ -48,6 +47,7 @@ interface CreditCardsAddressesStorage {
/**
* Deletes the credit card with the given [guid].
*
* @param guid Unique identifier for the desired credit card.
* @return True if the deletion did anything, false otherwise.
*/
suspend fun deleteCreditCard(guid: String): Boolean
Expand Down Expand Up @@ -104,6 +104,18 @@ interface CreditCardsAddressesStorage {
* @param guid Unique identifier for the desired address.
*/
suspend fun touchAddress(guid: String)

/**
* Clears out all address local state, bringing us back to the state before the first
* write (or sync).
*/
suspend fun wipeLocalAddresses()

/**
* Clears out all credit card local state, bringing us back to the state before the first
* write (or sync).
*/
suspend fun wipeLocalCreditCards()
}

/**
Expand All @@ -128,10 +140,10 @@ data class CreditCard(
val expiryMonth: Long,
val expiryYear: Long,
val cardType: String,
val timeCreated: Long,
val timeLastUsed: Long?,
val timeLastModified: Long,
val timesUsed: Long
val timeCreated: Long = 0L,
val timeLastUsed: Long? = 0L,
val timeLastModified: Long = 0L,
val timesUsed: Long = 0L
) : Parcelable

/**
Expand Down Expand Up @@ -223,6 +235,44 @@ data class UpdatableAddressFields(
val email: String
)

/**
* Provides a method for checking whether or not a given credit card can be stored.
*/
interface CreditCardValidationDelegate {

/**
* The result from validating a given [CreditCard] against the credit card storage. This will
* include whether or not it can be created, updated, or neither, along with an explanation
* of any errors.
*/
sealed class Result {
/**
* Indicates that the [CreditCard] does not currently exist in the storage, and a new
* credit card entry can be created.
*/
object CanBeCreated : Result()

/**
* Indicates that a matching [CreditCard] was found in the storage, and the [CreditCard]
* can be used to update its information.
*/
data class CanBeUpdated(val foundCreditCard: CreditCard) : Result()

/**
* The [CreditCard] cannot be saved or updated.
*/
object Error : Result()
}

/**
* Determines whether a [CreditCard] can be added or updated in the credit card storage.
*
* @param creditCard [CreditCard] to be added or updated in the credit card storage.
* @return [Result] that indicates whether or not the [CreditCard] should be saved or updated.
*/
suspend fun validate(creditCard: CreditCard): Result
}

/**
* Used to handle [Address] and [CreditCard] storage so that the underlying engine doesn't have to.
* An instance of this should be attached to the Gecko runtime in order to be used.
Expand All @@ -232,22 +282,30 @@ interface CreditCardsAddressesStorageDelegate {
/**
* Returns all stored addresses. This is called when the engine believes an address field
* should be autofilled.
*
* @return A list of all stored addresses.
*/
fun onAddressesFetch(): Deferred<List<Address>>
suspend fun onAddressesFetch(): List<Address>

/**
* Saves the given address to storage.
*
* @param address [Address] to be saved or updated in the address storage.
*/
fun onAddressSave(address: Address)

/**
* Returns all stored credit cards. This is called when the engine believes a credit card
* field should be autofilled.
*
* @return A list of all stored credit cards.
*/
fun onCreditCardsFetch(): Deferred<List<CreditCard>>
suspend fun onCreditCardsFetch(): List<CreditCard>

/**
* Saves the given credit card to storage.
*
* @param creditCard [CreditCard] to be saved or updated in the credit card storage.
*/
fun onCreditCardSave(creditCard: CreditCard)
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,22 @@ class AutofillCreditCardsAddressesStorage(
coroutineContext.cancel()
conn.close()
}

override suspend fun wipeLocalAddresses() = withContext(coroutineContext) {
val storage = conn.getStorage()

storage.getAllAddresses().forEach { address ->
storage.deleteAddress(address.guid)
}
}

override suspend fun wipeLocalCreditCards() = withContext(coroutineContext) {
val storage = conn.getStorage()

storage.getAllCreditCards().forEach { creditCard ->
storage.deleteCreditCard(creditCard.guid)
}
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.service.sync.autofill

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import mozilla.components.concept.storage.CreditCard
import mozilla.components.concept.storage.CreditCardValidationDelegate
import mozilla.components.concept.storage.CreditCardValidationDelegate.Result
import mozilla.components.concept.storage.CreditCardsAddressesStorage

/**
* A delegate that will check against the [CreditCardsAddressesStorage] to determine if a given
* [CreditCard] can be persisted and returns information about why it can or cannot.
*/
class DefaultCreditCardValidationDelegate(
private val storage: Lazy<CreditCardsAddressesStorage>
) : CreditCardValidationDelegate {

private val coroutineContext by lazy { Dispatchers.IO }

override suspend fun validate(creditCard: CreditCard): Result =
withContext(coroutineContext) {
val creditCards = storage.value.getAllCreditCards()

val foundCreditCard = if (creditCards.isEmpty()) {
// No credit cards exist in the storage, create a new credit card to the storage.
null
} else {
// Attempt to find a credit card with a matching guid or card number with the
// new credit card. If an existing credit card is found, update it with the new
// credit card entry. Otherwise, create a new credit card entry in the storage.
creditCards.find { it.guid == creditCard.guid || it.cardNumber == creditCard.cardNumber }
}

if (foundCreditCard == null) Result.CanBeCreated else Result.CanBeUpdated(
foundCreditCard
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
package mozilla.components.service.sync.autofill

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mozilla.components.concept.storage.Address
import mozilla.components.concept.storage.CreditCard
import mozilla.components.concept.storage.CreditCardValidationDelegate
import mozilla.components.concept.storage.CreditCardsAddressesStorage
import mozilla.components.concept.storage.CreditCardsAddressesStorageDelegate

Expand All @@ -21,23 +22,37 @@ class GeckoCreditCardsAddressesStorageDelegate(
private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO)
) : CreditCardsAddressesStorageDelegate {

override fun onAddressesFetch(): Deferred<List<Address>> {
return scope.async {
storage.value.getAllAddresses()
}
override suspend fun onAddressesFetch(): List<Address> = withContext(scope.coroutineContext) {
storage.value.getAllAddresses()
}

override fun onAddressSave(address: Address) {
TODO("Not yet implemented")
}

override fun onCreditCardsFetch(): Deferred<List<CreditCard>> {
return scope.async {
override suspend fun onCreditCardsFetch(): List<CreditCard> =
withContext(scope.coroutineContext) {
storage.value.getAllCreditCards()
}
}

override fun onCreditCardSave(creditCard: CreditCard) {
TODO("Not yet implemented")
val validationDelegate = DefaultCreditCardValidationDelegate(storage)

scope.launch {
when (val result = validationDelegate.validate(creditCard)) {
is CreditCardValidationDelegate.Result.CanBeCreated -> {
storage.value.addCreditCard(creditCard.intoUpdatableCreditCardFields())
}
is CreditCardValidationDelegate.Result.CanBeUpdated -> {
storage.value.updateCreditCard(
guid = result.foundCreditCard.guid,
creditCardFields = creditCard.intoUpdatableCreditCardFields()
)
}
is CreditCardValidationDelegate.Result.Error -> {
// Do nothing since an error occurred and the credit card cannot be saved.
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,30 @@ internal fun mozilla.appservices.autofill.CreditCard.into(): CreditCard {
timesUsed = this.timesUsed
)
}

internal fun Address.intoUpdatableAddressFields(): mozilla.components.concept.storage.UpdatableAddressFields {
return mozilla.components.concept.storage.UpdatableAddressFields(
givenName = this.givenName,
additionalName = this.additionalName,
familyName = this.familyName,
organization = this.organization,
streetAddress = this.streetAddress,
addressLevel3 = this.addressLevel3,
addressLevel2 = this.addressLevel2,
addressLevel1 = this.addressLevel1,
postalCode = this.postalCode,
country = this.country,
tel = this.tel,
email = this.email
)
}

internal fun CreditCard.intoUpdatableCreditCardFields(): mozilla.components.concept.storage.UpdatableCreditCardFields {
return mozilla.components.concept.storage.UpdatableCreditCardFields(
billingName = this.billingName,
cardNumber = this.cardNumber,
expiryMonth = this.expiryMonth,
expiryYear = this.expiryYear,
cardType = this.cardType
)
}
Loading

0 comments on commit 719b601

Please sign in to comment.