Sunday, December 22, 2024

Implementing “Add to Pockets” in an Android Software

Samsung Pockets is a quick and safe digital pockets utility bundled with hundreds of thousands of Samsung Galaxy units worldwide. Its streamlined performance permits customers so as to add and retailer numerous passes, tickets, and credentials multi function place. You’ll be able to design customized Pockets playing cards, akin to for boarding passes, tickets, coupons, present playing cards and loyalty playing cards, to your service and problem them to customers who can add them to the Samsung Pockets utility on their system.

Samsung Pockets playing cards are signed with the RS256 uneven algorithm. The RS256 signing algorithm requires a non-public key, which is a private credential that must not ever be shared within the utility. Consequently, a separate server utility is required to retailer the important thing and signal the Pockets card knowledge (CData).

When the person faucets the “Add to Samsung Pockets” button within the utility, the server utility creates and indicators the Pockets card knowledge, then returns a JWT token that’s used so as to add the Pockets card to the person’s Samsung Pockets utility.

undefined

Determine 1: “Add to Pockets” stream

undefined

This tutorial makes use of Kotlin to reveal learn how to implement the “Add to Pockets” function in an Android cellular utility that provides film tickets to Samsung Pockets. It additionally exhibits learn how to generate the Pockets playing cards utilizing a Spring Boot server. You’ll be able to comply with together with the tutorial by downloading the pattern code information.

Develop the cellular utility

To implement the “Add to Pockets” button within the cellular utility, it’s essential to configure the applying, implement the applying UI, and outline the applying logic for the button.

Configuring the cellular utility challenge

Create an utility challenge and configure it to hook up with and talk with the server by REST API requests:

  1. In Android Studio, create a brand new challenge.
  2. To implement REST API help within the utility, add the next Retrofit library dependencies to the applying’s “construct.gradle” file:
    'com.squareup.retrofit2:retrofit:2.11.0'
    'com.squareup.retrofit2:converter-gson: 2.11.0'
    
  3. To allow communication with the server, add the next permissions to the “AndroidManifest.xml” file:
    <uses-permission android:title="android.permission.INTERNET"/>
    <uses-permission android:title="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:title="android.permission.ACCESS_WIFI_STATE"/>
    
  4. To allow testing with a neighborhood non-HTTPS server, add the next attribute to the “utility” ingredient within the “AndroidManifest.xml” file:
    android:usesCleartextTraffic="true"
    

Implementing the applying UI

The applying UI consists of two screens: the film tickets checklist and a ticket element web page.

  1. Within the utility code, outline a Film knowledge class that comprises particulars for the film tickets that may be added to Samsung Pockets.

    knowledge class Film(
        val title:String,
        val studio:String,
        val ticketNumber:String,
    )
    
  2. Create a RecyclerView that shows the checklist of film tickets as buttons on the principle display screen. Every button has a listener that opens the element web page for the ticket. You’ll be able to research the implementation particulars within the pattern code.

  3. To verify whether or not the system helps Samsung Pockets, ship an HTTP GET request to the next endpoint, the place Construct.MODEL is the mannequin of the system:

    https://api-us3.mpay.samsung.com/pockets/cmn/v2.0/system/accessible?serviceType=WALLET&modelName=${Construct.MODEL}
    
  4. Create the ticket element web page format within the “activity_movie_detail.xml” file. The “Add to Pockets” button is applied on this web page.

    undefined

    Determine 2: Element web page format

    undefined

    Implement the ticket element web page performance within the MovieDetailActivity exercise class within the utility code.

    For this demonstration, the film ticket knowledge is predefined within the utility code. In an actual utility, the information is normally retrieved in actual time from an exterior database.

    val movieLists = listOf<Film>(
        Film("The Pockets", "Samsung Studios", "A-01"),
        Film("Crying Sea", "Laplace Studio","H-07"),
        Film("Canoe", "Terra Productions", "R-03")
    )
    val place:Int = intent.getIntExtra("moviePosition", 0)
    val film:Film = movieLists[position]
    
    binding.mvNameText.textual content = film.title
    binding.mvStudioText.textual content = film.studio
    binding.mvTicketNumber.textual content = "Ticket: ${film.ticketNumber}"
    
    binding.addToWalletButton.setOnClickListener {
        // Request server to generate card knowledge
        // Retrieve signed card knowledge from server
        // Add card to Samsung Pockets utility
    }
    
  5. When the person faucets the “Add to Pockets” button, OnClickListener() is triggered. Its performance is outlined later on this tutorial.

Connecting to the server

To speak with the server:

  1. Within the TokenResponse class, outline the construction of the JSON response to be acquired from the server. The standing area signifies whether or not the token era request was profitable, and the jwt area comprises the generated CData within the type of a JWT token.

    knowledge class TokenResponse(
        val standing: String,
        val jwt:String
    )
    
  2. Within the “ApiClient.kt” file, outline a RetrofitClient object that’s used to ascertain the reference to the server.

  3. Outline the ApiInterface interface, which defines the API request and response:

    • The API endpoint URL is BASE_URL/film/{ID}, the place {ID} is the film ticket ID to be added

    • The anticipated response from the endpoint is a TokenResponse object.

  4. Outline an ApiClient object that extends ApiInterface and creates a RetrofitClient occasion to ascertain the server connection.

    object RetrofitClient {
        personal const val BASE_URL = "http://192.xxx.xxx.xxx:8080" // Outline your server URL
    
        val retrofit: Retrofit by lazy {
            Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .construct()
        }
    }
    interface ApiInterface {
        @GET("/film/{id}")
        droop enjoyable getMovie(@Path("id") movieId:Int): Response<TokenResponse>
    }
    
    object ApiClient {
        val apiService: ApiInterface by lazy {
            RetrofitClient.retrofit.create(ApiInterface::class.java)
        }
    }
    

Including card knowledge to Samsung Pockets

To request CData era from the server and add the Pockets card to Samsung Pockets:

  1. Within the addToWalletButton.setOnClickListener() methodology inside the MovieDetailActivity class, ship a request to the server to generate CData for the chosen film ticket.

  2. If CData era is profitable, so as to add the film ticket to Samsung Pockets, ship an HTTP request containing the CData token to the next endpoint URL: https://a.swallet.hyperlink/atw/v1/{Card Id}#Clip?cdata={CData token}

  3. For extra details about this endpoint, see Information Transmit Hyperlink.

binding.addToWalletButton.setOnClickListener {
    CoroutineScope(Dispatchers.Important).launch {
        val response = ApiClient.apiService.getMovie(place)
        if(response.isSuccessful && response.physique()!=null){
            startActivity(Intent(
                Intent.ACTION_VIEW,
                Uri.parse("http://a.swallet.hyperlink/atw/v1/3aabbccddee00#Clip?cdata=${response.physique()!!.jwt}")))
// Change '3aabbccddee00' half together with your card ID 
        }
    }
}

ObserveThe generated CData is legitimate for 30 seconds, so it is suggested to generate the CData solely when the “Add to Samsung Pockets” button is clicked. If the CData has expired by the point the token is distributed to Samsung Pockets, the person can obtain a “Request Timed Out” error.

Generate signed Pockets card knowledge

The server utility should be configured to obtain the cardboard knowledge request from the cellular utility and return a signed JWT token. This a part of the tutorial makes use of the Spring Boot framework.

Configuring the server challenge

To create and configure a server utility to generate and signal Pockets card knowledge:

  1. Within the Spring Initializr software or any supported Java IDE, create a Spring Boot challenge and open the pattern code.
  2. To configure the server to obtain REST API requests from the cellular utility, add the “Spring Net” dependency to the challenge.
  3. Outline a Token knowledge class. Make certain it has the identical attributes because the TokenResponse knowledge class outlined within the cellular utility.

    knowledge class Token(val standing:String, val jwt:String
    
  4. Initialize a TokenController class that receives the incoming requests and returns a Token object in response.

    @RestController
    @RequestMapping("film")
    class TokenController {
        @GetMapping(path = ["/{movieId}"])
        enjoyable getMovie(@PathVariable movieId:Int): Token {
            return Token("success", "{DUMMY_CDATA}")
            // CData era logic
        }
    }
    

    The CData era and signing logic is described within the subsequent part.

Implementing card knowledge signing logic

For simpler understanding, this part describes a simplified implementation of the CData Technology Pattern Code.

  1. Within the server utility challenge, copy the next credential information to the “pattern/securities/” listing.
    • Samsung public key from the Samsung certificates (“Samsung.crt”)
    • Accomplice public key out of your companion certificates (“Accomplice.crt”)
    • Accomplice personal key from the personal key file (“Accomplice.key”)
  2. To deal with the certificates information and signing algorithms, add the next dependencies to the server utility’s “construct.gradle” file:
    implementation 'com.nimbusds:nimbus-jose-jwt:9.37.3'
    implementation 'org.bouncycastle:bcprov-jdk18on:1.77'
    
  3. In a brand new “JwtGen.kt” file, outline a readCertificate() methodology that reads the general public keys from the certificates and a readPrivateKey() methodology that reads the personal key from the important thing file.

    personal val PARTNER_ID = "4048012345678912345" // Change together with your companion ID
    
    personal val samsungPublicKey = 
        readCertificate(getStringFromFile("pattern/securities/Samsung.crt"))
    personal val partnerPublicKey = 
        readCertificate(getStringFromFile("pattern/securities/Accomplice.crt"))
    personal val partnerPrivateKey = 
        readPrivateKey(getStringFromFile("pattern/securities/Accomplice.key"))
    
    enjoyable readPrivateKey(key: String): PrivateKey {
        val keyByte = readKeyByte(key)
        lateinit var privateKey: PrivateKey
        val pkcs8Spec = PKCS8EncodedKeySpec(keyByte)
        strive {
            val kf = KeyFactory.getInstance("RSA")
            privateKey = kf.generatePrivate(pkcs8Spec)
        } catch (e: InvalidKeySpecException) {
            e.printStackTrace()
        } catch (e: NoSuchAlgorithmException) {
            e.printStackTrace()
        }
        return privateKey
    }
    
    enjoyable readCertificate(cert: String): PublicKey {
        lateinit var certificates: Certificates
        val keyByte = readKeyByte(cert)
        val `is`: InputStream = ByteArrayInputStream(keyByte)
        strive {
            val cf = CertificateFactory.getInstance("X.509")
            certificates = cf.generateCertificate(`is`)
        } catch (e: CertificateException) {
            e.printStackTrace()
        }
        return certificates.publicKey
    }
    
    personal enjoyable readKeyByte(key: String): ByteArray {
        val keyByte: ByteArray
        val bais = ByteArrayInputStream(key.toByteArray(StandardCharsets.UTF_8))
        val reader: Reader = InputStreamReader(bais, StandardCharsets.UTF_8)
        val pemReader = PemReader(reader)
        var pemObject: PemObject? = null
        strive {
            pemObject = pemReader.readPemObject()
        } catch (e: IOException) {
            e.printStackTrace()
        }
        keyByte = if (pemObject == null) {
            Base64.getDecoder().decode(key)
        } else {
            pemObject.content material
        }
        return keyByte
    }
    
    enjoyable getStringFromFile(path: String?): String {
        strive {
            val file =
                File(Objects.requireNonNull(ClassLoader.getSystemClassLoader().getResource(path)).file)
            return String(Information.readAllBytes(file.toPath()))
        } catch (e: IOException) {
            throw RuntimeException(e)
        }
    }
    

Producing card knowledge

CData token era is applied within the “JwtGen.kt” file:

  1. Learn the file containing uncooked JSON knowledge that defines the ticket knowledge construction.

    For this demonstration, use the “Ticket.json” file within the “pattern/payload/” listing of the CData era pattern code.

  2. Generate or fill within the required ticket particulars. For instance, the “{title}” and “{seatNumber}” fields are changed with the film title and seat quantity.

    For details about the entire JSON construction, see Pockets Playing cards.

  3. Convert the JSON knowledge to a JWE object.
  4. Encrypt the JWE object with the Samsung public key.
  5. Construct the customized JWS header for Samsung Pockets playing cards.
  6. Signal and validate the entire JWS object together with your companion personal and public key utilizing the RS256 uneven algorithm. That is the CData token.
personal val currentTimeMillis = System.currentTimeMillis()

personal val plainData:String = getStringFromFile("pattern/payload/Ticket.json")
    .exchange("{refId}", UUID.randomUUID().toString())
    .exchange("{language}", "en")
    .exchange("{createdAt}", currentTimeMillis.toString())
    .exchange("{updatedAt}", currentTimeMillis.toString())
    .exchange("{issueDate}", currentTimeMillis.toString())
    .exchange("{startDate}", (currentTimeMillis + TimeUnit.DAYS.toMillis(1)).toString())
    .exchange("{endDate}", (currentTimeMillis + TimeUnit.DAYS.toMillis(1) + +TimeUnit.HOURS.toMillis(2)).toString())

enjoyable generateCdata(movieName: String, movieTicktNo:String): String{
    // Modify knowledge as wanted
    val knowledge = plainData.exchange("{title}", ""$movieName"")
        .exchange("{seatNumber}",""$movieTicktNo"")

    //print(knowledge)

    return generate(PARTNER_ID, samsungPublicKey, partnerPublicKey, partnerPrivateKey, knowledge)
}

personal enjoyable generate(partnerId: String, samsungPublicKey: PublicKey, partnerPublicKey: PublicKey,
                     partnerPrivateKey: PrivateKey, knowledge: String): String {
    val jweEnc = EncryptionMethod.A128GCM
    val jweAlg = JWEAlgorithm.RSA1_5
    val jweHeader = JWEHeader.Builder(jweAlg, jweEnc).construct()
    val encryptor = RSAEncrypter(samsungPublicKey as RSAPublicKey)
    val jwe = JWEObject(jweHeader, Payload(knowledge))
    strive {
        jwe.encrypt(encryptor)
    } catch (e: JOSEException) {
        e.printStackTrace()
    }
    val payload = jwe.serialize()

    val jwsAlg = JWSAlgorithm.RS256
    val utc = System.currentTimeMillis()

    val jwsHeader = JWSHeader.Builder(jwsAlg)
        .contentType("CARD")
        .customParam("partnerId", partnerId)
        .customParam("ver", "2")
        .customParam("utc", utc)
        .construct()

    val jwsObj = JWSObject(jwsHeader, Payload(payload))

    val rsaJWK = RSAKey.Builder(partnerPublicKey as RSAPublicKey)
        .privateKey(partnerPrivateKey)
        .construct()

    val signer: JWSSigner
    strive {
        signer = RSASSASigner(rsaJWK)
        jwsObj.signal(signer)
    } catch (e: JOSEException) {
        e.printStackTrace()
    }
    return jwsObj.serialize()
}

Returning the signed token

Within the server utility code, when the server receives a request on the film/{movieID} endpoint, the TokenController class calls the JwtGen.generateCdata() methodology with the film ID, which generates and returns the CData JWT token within the API response.

On this tutorial, because the film ticket checklist was predefined within the cellular utility challenge, be certain the identical Film knowledge class and checklist are outlined right here too.

@RestController
@RequestMapping("film")
class TokenController {
    @GetMapping(path = ["/{movieId}"])
    enjoyable getMovie(@PathVariable movieId:Int): Token {
        val movieLists = listOf<Film>(
            Film("The Pockets", "Samsung Studios", "A-01"),
            Film("Crying Sea", "Laplace Studio","H-07"),
            Film("Canoe", "Terra Productions", "R-03")
        )
        
        if( movieId>2){
            // Implement your verification logic
            return Token("failure", "")
        }
        else{
            val cdata = JwtGen.generateCdata(movieLists[movieId].title, movieLists[movieId].ticketNumber)
            return Token("success", cdata)
        }
    }
}

Testing the applying

To check your “Add to Pockets” integration:

  1. Join the server and the cellular system to the identical community.
  2. Launch the server and cellular purposes.
  3. Within the cellular utility, faucet a film ticket within the checklist. Its element web page opens.
  4. Faucet Add to Samsung Pockets. The server generates and returns the CData token.
  5. The Samsung Pockets utility launches on the system and the film ticket data is added to it.
undefined

Determine 3: Ticket added to Samsung Pockets

undefined

Abstract

Implementing the “Add to Pockets” function allows your customers so as to add your digital content material, akin to tickets, passes, and loyalty playing cards, to the Samsung Pockets utility on their cellular system as Pockets playing cards. Along with implementing the “Add to Samsung Pockets” button in your cellular utility, it’s essential to additionally create a server utility that securely generates and indicators the Pockets card knowledge and returns it to the cellular utility for transmitting to Samsung Pockets.

For extra details about including “Add to Pockets” to your utility, see Implementing ATW button. You may also research the prolonged pattern utility (clicking this hyperlink downloads the pattern code) and the API reference.

When you’ve got questions on or need assistance with the data offered on this article, you’ll be able to share your queries on the Samsung Builders Discussion board. You may also contact us instantly for extra specialised help by the Samsung Developer Assist Portal.

Assets

Click on the hyperlinks under to obtain the pattern code.

  1. Android App Pattern Code
  2. Prolonged Android App Pattern Code
  3. CData Technology Server Pattern Code

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles