diff --git a/multipaz-doctypes/src/commonMain/kotlin/org/multipaz/documenttype/knowntypes/Loyalty.kt b/multipaz-doctypes/src/commonMain/kotlin/org/multipaz/documenttype/knowntypes/Loyalty.kt new file mode 100644 index 000000000..912f716a1 --- /dev/null +++ b/multipaz-doctypes/src/commonMain/kotlin/org/multipaz/documenttype/knowntypes/Loyalty.kt @@ -0,0 +1,121 @@ +package org.multipaz.documenttype.knowntypes + +import org.multipaz.cbor.toDataItem +import org.multipaz.cbor.toDataItemFullDate +import org.multipaz.documenttype.DocumentAttributeType +import org.multipaz.documenttype.DocumentType +import org.multipaz.documenttype.Icon +import org.multipaz.util.fromBase64Url +import kotlinx.datetime.LocalDate + +object Loyalty { + const val LOYALTY_DOCTYPE = "org.multipaz.loyalty.1" + const val LOYALTY_NAMESPACE = "org.multipaz.loyalty.1" + + /** + * Build the Loyalty ID Document Type. + */ + fun getDocumentType(): DocumentType { + return DocumentType.Builder("Loyalty") + .addMdocDocumentType(LOYALTY_DOCTYPE) + // Core holder data relevant for a loyalty card + // + .addMdocAttribute( + DocumentAttributeType.String, + "family_name", + "Family Name", + "Last name, surname, or primary identifier, of the document holder", + true, + LOYALTY_NAMESPACE, + Icon.PERSON, + SampleData.FAMILY_NAME.toDataItem() + ) + .addMdocAttribute( + DocumentAttributeType.String, + "given_name", + "Given Names", + "First name(s), other name(s), or secondary identifier, of the document holder", + true, + LOYALTY_NAMESPACE, + Icon.PERSON, + SampleData.GIVEN_NAME.toDataItem() + ) + .addMdocAttribute( + DocumentAttributeType.Picture, + "portrait", + "Photo of Holder", + "A reproduction of the document holder’s portrait.", + true, + LOYALTY_NAMESPACE, + Icon.ACCOUNT_BOX, + SampleData.PORTRAIT_BASE64URL.fromBase64Url().toDataItem() + ) + // Then the LoyaltyID specific data elements. + // + .addMdocAttribute( + DocumentAttributeType.String, + "membership_number", + "Membership ID", + "Person identifier of the Loyalty ID holder.", + false, + LOYALTY_NAMESPACE, + Icon.NUMBERS, + SampleData.PERSON_ID.toDataItem() + ) + .addMdocAttribute( + DocumentAttributeType.String, + "tier", + "Tier", + "Membership tier (basic, silver, gold, platinum, elite)", + false, + LOYALTY_NAMESPACE, + Icon.STARS, + "basic".toDataItem() + ) + .addMdocAttribute( + DocumentAttributeType.Date, + "issue_date", + "Date of Issue", + "Date when document was issued", + true, + LOYALTY_NAMESPACE, + Icon.CALENDAR_CLOCK, + LocalDate.parse(SampleData.ISSUE_DATE).toDataItemFullDate() + ) + .addMdocAttribute( + DocumentAttributeType.Date, + "expiry_date", + "Date of Expiry", + "Date when document expires", + true, + LOYALTY_NAMESPACE, + Icon.CALENDAR_CLOCK, + LocalDate.parse(SampleData.EXPIRY_DATE).toDataItemFullDate() + ) + // Finally for the sample requests. + // + .addSampleRequest( + id = "mandatory", + displayName = "Mandatory Data Elements", + mdocDataElements = mapOf( + LOYALTY_NAMESPACE to mapOf( + "family_name" to false, + "given_name" to false, + "portrait" to false, + "membership_number" to false, + "tier" to false, + "issue_date" to false, + "expiry_date" to false, + ) + ) + ) + .addSampleRequest( + id = "full", + displayName ="All Data Elements", + mdocDataElements = mapOf( + LOYALTY_NAMESPACE to mapOf() + ) + ) + .build() + } +} diff --git a/multipaz-doctypes/src/commonMain/kotlin/org/multipaz/documenttype/knowntypes/LoyaltyID.kt b/multipaz-doctypes/src/commonMain/kotlin/org/multipaz/documenttype/knowntypes/LoyaltyID.kt deleted file mode 100644 index 1d6187eb7..000000000 --- a/multipaz-doctypes/src/commonMain/kotlin/org/multipaz/documenttype/knowntypes/LoyaltyID.kt +++ /dev/null @@ -1,194 +0,0 @@ -package org.multipaz.documenttype.knowntypes - -import org.multipaz.cbor.toDataItem -import org.multipaz.cbor.toDataItemFullDate -import org.multipaz.documenttype.DocumentAttributeType -import org.multipaz.documenttype.DocumentType -import org.multipaz.documenttype.Icon -import org.multipaz.util.fromBase64Url -import kotlinx.datetime.LocalDate -import org.multipaz.cbor.buildCborMap - -object LoyaltyID { - const val LOYALTY_ID_DOCTYPE = "org.multipaz.loyality.1" - const val LOYALTY_ID_NAMESPACE = "org.multipaz.loyality.1" - - /** - * Build the Loyalty ID Document Type. - */ - fun getDocumentType(): DocumentType { - return DocumentType.Builder("Loyalty ID") - .addMdocDocumentType(LOYALTY_ID_DOCTYPE) - // First the data elements from ISO/IEC 23220-2. - // - .addMdocAttribute( - DocumentAttributeType.String, - "family_name", - "Family Name", - "Last name, surname, or primary identifier, of the document holder", - true, - LOYALTY_ID_NAMESPACE, - Icon.PERSON, - SampleData.FAMILY_NAME.toDataItem() - ) - .addMdocAttribute( - DocumentAttributeType.String, - "given_name", - "Given Names", - "First name(s), other name(s), or secondary identifier, of the document holder", - true, - LOYALTY_ID_NAMESPACE, - Icon.PERSON, - SampleData.GIVEN_NAME.toDataItem() - ) - .addMdocAttribute( - DocumentAttributeType.Date, // TODO: this is a more complex type - "birth_date", - "Date of Birth", - "Day, month and year on which the document holder was born. If unknown, approximate date of birth", - true, - LOYALTY_ID_NAMESPACE, - Icon.TODAY, - buildCborMap { - put("birth_date", LocalDate.parse(SampleData.BIRTH_DATE).toDataItemFullDate()) - } - ) - .addMdocAttribute( - DocumentAttributeType.Picture, - "portrait", - "Photo of Holder", - "A reproduction of the document holder’s portrait.", - true, - LOYALTY_ID_NAMESPACE, - Icon.ACCOUNT_BOX, - SampleData.PORTRAIT_BASE64URL.fromBase64Url().toDataItem() - ) - .addMdocAttribute( - DocumentAttributeType.Boolean, - "age_over_16", - "Older Than 16 Years", - "Indication whether the document holder is as old or older than 16", - false, - LOYALTY_ID_NAMESPACE, - Icon.TODAY, - SampleData.AGE_OVER_16.toDataItem() - ) - .addMdocAttribute( - DocumentAttributeType.Boolean, - "age_over_18", - "Older Than 18 Years", - "Indication whether the document holder is as old or older than 18", - false, - LOYALTY_ID_NAMESPACE, - Icon.TODAY, - SampleData.AGE_OVER_18.toDataItem() - ) - .addMdocAttribute( - DocumentAttributeType.Boolean, - "age_over_21", - "Older Than 21 Years", - "Indication whether the document holder is as old or older than 21", - false, - LOYALTY_ID_NAMESPACE, - Icon.TODAY, - SampleData.AGE_OVER_21.toDataItem() - ) - .addMdocAttribute( - DocumentAttributeType.IntegerOptions(Options.SEX_ISO_IEC_5218), - "sex", - "Sex", - "document holder’s sex", - false, - LOYALTY_ID_NAMESPACE, - Icon.EMERGENCY, - SampleData.SEX_ISO_5218.toDataItem() - ) - // Then the LoyaltyID specific data elements. - // - .addMdocAttribute( - DocumentAttributeType.String, - "membership_number", - "Membership ID", - "Person identifier of the Loyalty ID holder.", - false, - LOYALTY_ID_NAMESPACE, - Icon.NUMBERS, - SampleData.PERSON_ID.toDataItem() - ) - .addMdocAttribute( - DocumentAttributeType.Date, - "issue_date", - "Date of Issue", - "Date when document was issued", - true, - LOYALTY_ID_NAMESPACE, - Icon.CALENDAR_CLOCK, - LocalDate.parse(SampleData.ISSUE_DATE).toDataItemFullDate() - ) - .addMdocAttribute( - DocumentAttributeType.Date, - "expiry_date", - "Date of Expiry", - "Date when document expires", - true, - LOYALTY_ID_NAMESPACE, - Icon.CALENDAR_CLOCK, - LocalDate.parse(SampleData.EXPIRY_DATE).toDataItemFullDate() - ) - // Finally for the sample requests. - // - .addSampleRequest( - id = "age_over_18", - displayName ="Age Over 18", - mdocDataElements = mapOf( - LOYALTY_ID_NAMESPACE to mapOf( - "age_over_18" to false, - ) - ), - ) - .addSampleRequest( - id = "age_over_18_zkp", - displayName ="Age Over 18 (ZKP)", - mdocDataElements = mapOf( - LOYALTY_ID_NAMESPACE to mapOf( - "age_over_18" to false, - ) - ), - mdocUseZkp = true - ) - .addSampleRequest( - id = "age_over_18_and_portrait", - displayName ="Age Over 18 + Portrait", - mdocDataElements = mapOf( - LOYALTY_ID_NAMESPACE to mapOf( - "age_over_18" to false, - "portrait" to false - ) - ), - ) - .addSampleRequest( - id = "mandatory", - displayName = "Mandatory Data Elements", - mdocDataElements = mapOf( - LOYALTY_ID_NAMESPACE to mapOf( - "family_name" to false, - "given_name" to false, - "birth_date" to false, - "portrait" to false, - "age_over_18" to false, - "membership_number" to false, - "issue_date" to false, - "expiry_date" to false, - ) - ) - ) - .addSampleRequest( - id = "full", - displayName ="All Data Elements", - mdocDataElements = mapOf( - LOYALTY_ID_NAMESPACE to mapOf() - ) - ) - .build() - } -} diff --git a/multipaz-openid4vci-server/src/main/java/org/multipaz/openid4vci/credential/CredentialFactory.kt b/multipaz-openid4vci-server/src/main/java/org/multipaz/openid4vci/credential/CredentialFactory.kt index fa2980097..10aebb3f4 100644 --- a/multipaz-openid4vci-server/src/main/java/org/multipaz/openid4vci/credential/CredentialFactory.kt +++ b/multipaz-openid4vci-server/src/main/java/org/multipaz/openid4vci/credential/CredentialFactory.kt @@ -11,7 +11,7 @@ import org.multipaz.cbor.DataItem import org.multipaz.crypto.X509CertChain import org.multipaz.documenttype.knowntypes.AgeVerification import org.multipaz.documenttype.knowntypes.EUPersonalID -import org.multipaz.documenttype.knowntypes.LoyaltyID +import org.multipaz.documenttype.knowntypes.Loyalty import org.multipaz.openid4vci.request.wellKnownOpenidCredentialIssuer /** @@ -62,7 +62,7 @@ internal interface CredentialFactory { CredentialFactoryUtopiaNaturatization(), CredentialFactoryUtopiaMovieTicket(), CredentialFactoryAgeVerification(), - CredentialFactoryUtopiaLoyaltyID(), + CredentialFactoryUtopiaLoyalty(), ) factories.forEach { it.initialize() } registeredFactories = RegisteredFactories( @@ -99,7 +99,7 @@ internal data class Openid4VciFormatMdoc(val docType: String) : Openid4VciFormat internal val openId4VciFormatMdl = Openid4VciFormatMdoc(DrivingLicense.MDL_DOCTYPE) internal val openId4VciFormatPid = Openid4VciFormatMdoc(EUPersonalID.EUPID_DOCTYPE) internal val openId4VciFormatAv = Openid4VciFormatMdoc(AgeVerification.AV_DOCTYPE) -internal val openId4VciFormatLoyaltyId = Openid4VciFormatMdoc(LoyaltyID.LOYALTY_ID_DOCTYPE) +internal val openId4VciFormatLoyalty = Openid4VciFormatMdoc(Loyalty.LOYALTY_DOCTYPE) internal data class Openid4VciFormatSdJwt(val vct: String) : Openid4VciFormat() { override val id: String get() = "dc+sd-jwt" diff --git a/multipaz-openid4vci-server/src/main/java/org/multipaz/openid4vci/credential/CredentialFactoryUtopiaLoyaltyID.kt b/multipaz-openid4vci-server/src/main/java/org/multipaz/openid4vci/credential/CredentialFactoryUtopiaLoyalty.kt similarity index 82% rename from multipaz-openid4vci-server/src/main/java/org/multipaz/openid4vci/credential/CredentialFactoryUtopiaLoyaltyID.kt rename to multipaz-openid4vci-server/src/main/java/org/multipaz/openid4vci/credential/CredentialFactoryUtopiaLoyalty.kt index 2b5dbefc6..bb3fa7499 100644 --- a/multipaz-openid4vci-server/src/main/java/org/multipaz/openid4vci/credential/CredentialFactoryUtopiaLoyaltyID.kt +++ b/multipaz-openid4vci-server/src/main/java/org/multipaz/openid4vci/credential/CredentialFactoryUtopiaLoyalty.kt @@ -1,20 +1,12 @@ package org.multipaz.openid4vci.credential -import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.LocalDate -import kotlinx.datetime.TimeZone -import kotlinx.datetime.atStartOfDayIn -import kotlinx.datetime.plus -import kotlinx.datetime.toLocalDateTime -import kotlinx.datetime.yearsUntil import org.multipaz.cbor.Bstr import org.multipaz.cbor.Cbor import org.multipaz.cbor.DataItem import org.multipaz.cbor.RawCbor -import org.multipaz.cbor.Simple import org.multipaz.cbor.Tagged import org.multipaz.cbor.Tstr -import org.multipaz.cbor.Uint import org.multipaz.cbor.buildCborMap import org.multipaz.cbor.toDataItem import org.multipaz.cbor.toDataItemFullDate @@ -26,14 +18,13 @@ import org.multipaz.crypto.EcPrivateKey import org.multipaz.crypto.EcPublicKey import org.multipaz.crypto.X509Cert import org.multipaz.crypto.X509CertChain -import org.multipaz.documenttype.knowntypes.LoyaltyID +import org.multipaz.documenttype.knowntypes.Loyalty import org.multipaz.mdoc.issuersigned.buildIssuerNamespaces import org.multipaz.mdoc.mso.MobileSecurityObjectGenerator import org.multipaz.rpc.backend.BackendEnvironment import org.multipaz.rpc.backend.Resources import org.multipaz.util.toBase64Url import kotlin.random.Random -import kotlin.random.nextInt import kotlin.time.Clock import kotlin.time.Duration.Companion.days import kotlin.time.Instant @@ -41,7 +32,7 @@ import kotlin.time.Instant /** * Factory for LoyaltyID credentials according to ISO/IEC TS 23220-4 (E) operational phase - Annex C Photo ID v2 */ -internal class CredentialFactoryUtopiaLoyaltyID : CredentialFactoryBase() { +internal class CredentialFactoryUtopiaLoyalty : CredentialFactoryBase() { override val offerId: String get() = "utopia_wholesale" @@ -49,7 +40,7 @@ internal class CredentialFactoryUtopiaLoyaltyID : CredentialFactoryBase() { get() = "wholesale" override val format: Openid4VciFormat - get() = openId4VciFormatLoyaltyId + get() = openId4VciFormatLoyalty override val requireClientAttestation: Boolean get() = false @@ -76,7 +67,6 @@ internal class CredentialFactoryUtopiaLoyaltyID : CredentialFactoryBase() { val resources = BackendEnvironment.getInterface(Resources::class)!! val coreData = data["core"] - val dateOfBirth = coreData["birth_date"].asDateString val portrait = if (coreData.hasKey("portrait")) { coreData["portrait"].asBstr } else { @@ -89,7 +79,7 @@ internal class CredentialFactoryUtopiaLoyaltyID : CredentialFactoryBase() { val validUntil = validFrom + 30.days // Generate an MSO and issuer-signed data for this authentication key. - val docType = LoyaltyID.LOYALTY_ID_DOCTYPE + val docType = Loyalty.LOYALTY_DOCTYPE val msoGenerator = MobileSecurityObjectGenerator( Algorithm.SHA256, docType, @@ -97,11 +87,6 @@ internal class CredentialFactoryUtopiaLoyaltyID : CredentialFactoryBase() { ) msoGenerator.setValidityInfo(timeSigned, validFrom, validUntil, null) - val timeZone = TimeZone.currentSystemDefault() - val dateOfBirthInstant = dateOfBirth.atStartOfDayIn(timeZone) - // Calculate age-based flags - val ageOver18 = now > dateOfBirthInstant.plus(18, DateTimeUnit.YEAR, timeZone) - val ageOver21 = now > dateOfBirthInstant.plus(21, DateTimeUnit.YEAR, timeZone) val records = data["records"] if (!records.hasKey("wholesale")) { throw IllegalArgumentException("No wholesale membership card is issued to this person") @@ -112,6 +97,11 @@ internal class CredentialFactoryUtopiaLoyaltyID : CredentialFactoryBase() { } else { (1000000 + Random.nextInt(9000000)).toString() } + val tier = if (loyaltyIDData.hasKey("tier")) { + loyaltyIDData["tier"].asTstr + } else { + "basic" + } val issueDate = if (loyaltyIDData.hasKey("issue_date")) { loyaltyIDData["issue_date"].asDateString } else { @@ -125,19 +115,15 @@ internal class CredentialFactoryUtopiaLoyaltyID : CredentialFactoryBase() { val issuerNamespaces = buildIssuerNamespaces { // Combined LoyaltyID namespace (all data elements) - addNamespace(LoyaltyID.LOYALTY_ID_NAMESPACE) { + addNamespace(Loyalty.LOYALTY_NAMESPACE) { // Core personal data addDataElement("family_name", coreData["family_name"]) addDataElement("given_name", coreData["given_name"]) addDataElement("portrait", Bstr(portrait)) - addDataElement("birth_date", dateOfBirth.toDataItemFullDate()) - - // Age-based flags - addDataElement("age_over_18", if (ageOver18) Simple.TRUE else Simple.FALSE) - addDataElement("age_over_21", if (ageOver21) Simple.TRUE else Simple.FALSE) // LoyaltyID specific data addDataElement("membership_number", Tstr(membershipId)) + addDataElement("tier", Tstr(tier)) addDataElement("issue_date", issueDate.toDataItemFullDate()) addDataElement("expiry_date", expiryDate.toDataItemFullDate()) } @@ -190,6 +176,6 @@ internal class CredentialFactoryUtopiaLoyaltyID : CredentialFactoryBase() { } companion object Companion { - const val TAG = "CredentialFactoryUtopiaLoyaltyID" + const val TAG = "CredentialFactoryUtopiaLoyalty" } } \ No newline at end of file diff --git a/multipaz-records-server/src/main/java/org/multipaz/records/data/recordTypes.kt b/multipaz-records-server/src/main/java/org/multipaz/records/data/recordTypes.kt index 6dc6dcc32..4b1e76cbc 100644 --- a/multipaz-records-server/src/main/java/org/multipaz/records/data/recordTypes.kt +++ b/multipaz-records-server/src/main/java/org/multipaz/records/data/recordTypes.kt @@ -350,6 +350,12 @@ val recordTypes = RecordType.buildMap { description = "Person identifier of the Loyalty ID holder", icon = Icon.NUMBERS, ) + addString( + identifier = "tier", + displayName = "Tier", + description = "Membership tier (basic, silver, gold, platinum, elite)", + icon = Icon.STARS, + ) addDate( identifier = "issue_date", displayName = "Date of Issue", diff --git a/multipaz-verifier-server/src/main/java/org/multipaz/verifier/request/verifier.kt b/multipaz-verifier-server/src/main/java/org/multipaz/verifier/request/verifier.kt index 8cfea71b4..91aae3190 100644 --- a/multipaz-verifier-server/src/main/java/org/multipaz/verifier/request/verifier.kt +++ b/multipaz-verifier-server/src/main/java/org/multipaz/verifier/request/verifier.kt @@ -80,7 +80,7 @@ import org.multipaz.crypto.X509Cert import org.multipaz.crypto.X509KeyUsage import org.multipaz.documenttype.knowntypes.AgeVerification import org.multipaz.documenttype.knowntypes.IDPass -import org.multipaz.documenttype.knowntypes.LoyaltyID +import org.multipaz.documenttype.knowntypes.Loyalty import org.multipaz.mdoc.request.DocRequestInfo import org.multipaz.mdoc.request.ZkRequest import org.multipaz.mdoc.request.buildDeviceRequestSuspend @@ -295,7 +295,7 @@ private val documentTypeRepo: DocumentTypeRepository by lazy { repo.addDocumentType(UtopiaMovieTicket.getDocumentType()) repo.addDocumentType(IDPass.getDocumentType()) repo.addDocumentType(AgeVerification.getDocumentType()) - repo.addDocumentType(LoyaltyID.getDocumentType()) + repo.addDocumentType(Loyalty.getDocumentType()) repo } diff --git a/samples/testapp/src/commonMain/kotlin/org/multipaz/testapp/App.kt b/samples/testapp/src/commonMain/kotlin/org/multipaz/testapp/App.kt index c83b3c8b1..0d4ff6699 100644 --- a/samples/testapp/src/commonMain/kotlin/org/multipaz/testapp/App.kt +++ b/samples/testapp/src/commonMain/kotlin/org/multipaz/testapp/App.kt @@ -123,7 +123,7 @@ import org.multipaz.document.AbstractDocumentMetadata import org.multipaz.document.DocumentMetadata import org.multipaz.document.buildDocumentStore import org.multipaz.documenttype.knowntypes.AgeVerification -import org.multipaz.documenttype.knowntypes.LoyaltyID +import org.multipaz.documenttype.knowntypes.Loyalty import org.multipaz.documenttype.knowntypes.IDPass import org.multipaz.facematch.FaceMatchLiteRtModel import org.multipaz.mdoc.zkp.ZkSystemRepository @@ -141,7 +141,6 @@ import org.multipaz.testapp.provisioning.ProvisioningSupport import org.multipaz.testapp.ui.DcRequestScreen import org.multipaz.util.Platform import org.multipaz.testapp.ui.FaceMatchScreen -import org.multipaz.testapp.ShowResponseMetadata import org.multipaz.testapp.ui.ShowResponseScreen import org.multipaz.testapp.ui.TrustManagerScreen import org.multipaz.testapp.ui.TrustPointViewerScreen @@ -320,7 +319,7 @@ class App private constructor (val promptModel: PromptModel) { documentTypeRepository.addDocumentType(UtopiaMovieTicket.getDocumentType()) documentTypeRepository.addDocumentType(IDPass.getDocumentType()) documentTypeRepository.addDocumentType(AgeVerification.getDocumentType()) - documentTypeRepository.addDocumentType(LoyaltyID.getDocumentType()) + documentTypeRepository.addDocumentType(Loyalty.getDocumentType()) } private suspend fun documentStoreInit() { diff --git a/samples/testapp/src/commonMain/kotlin/org/multipaz/testapp/TestAppUtils.kt b/samples/testapp/src/commonMain/kotlin/org/multipaz/testapp/TestAppUtils.kt index f662053f6..e8d520c26 100644 --- a/samples/testapp/src/commonMain/kotlin/org/multipaz/testapp/TestAppUtils.kt +++ b/samples/testapp/src/commonMain/kotlin/org/multipaz/testapp/TestAppUtils.kt @@ -41,7 +41,7 @@ import org.multipaz.document.DocumentStore import org.multipaz.documenttype.DocumentCannedRequest import org.multipaz.documenttype.DocumentType import org.multipaz.documenttype.knowntypes.AgeVerification -import org.multipaz.documenttype.knowntypes.LoyaltyID +import org.multipaz.documenttype.knowntypes.Loyalty import org.multipaz.documenttype.knowntypes.DrivingLicense import org.multipaz.documenttype.knowntypes.EUPersonalID import org.multipaz.documenttype.knowntypes.PhotoID @@ -157,7 +157,7 @@ object TestAppUtils { EUPersonalID.getDocumentType(), UtopiaMovieTicket.getDocumentType(), AgeVerification.getDocumentType(), - LoyaltyID.getDocumentType(), + Loyalty.getDocumentType(), ) suspend fun provisionTestDocuments( @@ -421,7 +421,7 @@ object TestAppUtils { deviceKeyAlgorithm, deviceKeyMacAlgorithm, numCredentialsPerDomain, - LoyaltyID.getDocumentType(), + Loyalty.getDocumentType(), "Erika", "Erika's Loyalty ID", Res.drawable.card_utopia_wholesale