Skip to content

Commit 1b382e6

Browse files
author
Craig Christenson
committed
- added hide card feature
- update hashing algorithim - example app bug fixes
1 parent 65b6d46 commit 1b382e6

14 files changed

Lines changed: 185 additions & 51 deletions

File tree

README.md

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ private fun showPaymentOptions() {
3131

3232
**parameters for TwoCheckoutPaymentOptions class:**
3333

34-
this // activty context
34+
this // activity context
3535
payOptionList // ArrayList that contains the payment options you want to display to the user
3636
::onPayMethodSelected // callback method to receive the payment option selected by the user
3737

@@ -80,7 +80,7 @@ val customizationParam = loadFormPreferences()
8080
cardPaymentForm.displayPaymentForm()
8181
```
8282
83-
**sample callback method for receiving payment card token (::onCreditCardInput) and startin the card payment flow api call**
83+
**sample callback method for receiving payment card token (::onCreditCardInput) and starting the card payment flow api call**
8484
```kotlin
8585
private fun onCreditCardInput(cardPaymentToken:String) {
8686
if (cardPaymentToken.isEmpty()){
@@ -185,7 +185,7 @@ private fun onCreditCardInput(cardPaymentToken:String) {
185185
if (result.resultCode == ThreedsManager.threedsResultCode) {
186186
if (result.data!=null) {
187187
result.data?.let {
188-
//our sdk will return the transaction reference number here after the 3ds is completes
188+
//our sdk will return the transaction reference number here after the 3ds process completes
189189
//note that this receiver only indicates that the 3ds flow is over and does not indicate if it was succesfull or not
190190
//so another api call to get the status of the transaction is required
191191
val refNO = it.getStringExtra(ThreedsManager.keyRefNO)?:""
@@ -204,7 +204,7 @@ private fun onCreditCardInput(cardPaymentToken:String) {
204204
}
205205
```
206206

207-
**explained sample code for extracting the 3ds auth url from carp payment api response:**
207+
**explained sample code for extracting the 3ds auth url from card payment api response:**
208208
```kotlin
209209
private fun getThreedsUrl(response: String): String {
210210
val responseJson = JSONObject(response)
@@ -235,11 +235,11 @@ The TwoCheckoutPaymentForm class contains a method that can be used to generate
235235
fun getCardPaymentToken(merchantCode:String,cardInputObject: CreditCardInputResult,onTokenReady: (token:String) -> Unit)
236236
```
237237

238-
`merchantCode //your merchant code`
238+
`merchantCode - your merchant code`
239239

240-
`cardInputObject //data object that contains all the required card data for tokenization(card nr, cvv, expiryData, name)`
240+
`cardInputObject - data object that contains all the required card data for tokenization(card nr, cvv, expiryData, name)`
241241

242-
`onTokenReady //simple callback funtion that will be called by 2CO sdk with the token parameter once the process is complete`
242+
`onTokenReady - simple callback funtion that will be called by 2CO sdk with the token parameter once the process is complete`
243243

244244
Please note that this is a blocking function that needs to run on background thread.
245245

@@ -252,7 +252,7 @@ The code sample below explains how this function is used:
252252
cardPayData.creditCard = ""// card nr
253253
cardPayData.cvv ="" //card cvc cvv
254254
cardInputObject.cardData = cardPayData
255-
TwoCheckoutPaymentForm.getCardPaymentToken(SettingsActivity.getMerchantCode(this),cardInputObject,::onCreditCardInput)
255+
TwoCheckoutPaymentForm.getCardPaymentToken(SettingsActivity.getMerchantCode(this,cardInputObject,::onCreditCardInput))
256256
```
257257

258258
**Paypal payment flow**
@@ -285,16 +285,20 @@ After api call completes with success we can retrieve the paypal authorization l
285285
//PaypalStarter(ctx: Context, paypalURL: String)
286286
ctx : Merchant activity context
287287
paypalURL : paypal authorization url received in the initial api call
288+
289+
//sample code for starting the paypal screen
290+
if(paypalUrl.isEmpty()){
291+
progressDialog.dismiss()
292+
}
288293

289294
val mPaypalStarter = PaypalStarter(this,paypalUrl)
290295

291-
//display the paypal authorization screen from 2CO sdk
292-
mPaypalStarter.displayPaypalScreen(paypalReceiver)
296+
//display the paypal authorization screen from 2CO sdk, use one of the following method signatures
297+
mPaypalStarter.displayPaypalScreen(paypalReceiver,"www.2checkout.com","www.2checkout.com")
293298

294299
displayPaypalScreen(resultObject: ActivityResultLauncher<Intent>)
295300
//resultObject - ActivityResultLauncher<Intent> used to retrieve the result from 2CO sdk
296301

297-
298302
//Sample code for creating the object to receive the result
299303

300304
//since this is an lifecycle owner it needs to be created and registered before merchant activity or fragment is created like in the example below

TwoCheckoutAndroidSDK/src/main/java/com/twocheckout/connectors/datapack/PaymentConfigurationData.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class PaymentConfigurationData(
1313
var displayPrice:String= price
1414
var payButtonText:String = ""
1515
var merchantCodeParam:String = merchantCode
16+
var hideCard = false
1617
var displayCustomization: FormUICustomizationData = displayUICustomization
1718

1819
}

TwoCheckoutAndroidSDK/src/main/java/com/twocheckout/connectors/payments/paypal/PayPalWebClient.kt

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,21 @@ import java.net.URLDecoder
99

1010
class PayPalWebClient(onConfirmationDone: (MutableMap<String, String>) -> Unit): WebViewClient() {
1111
val sendConfirmationResult = onConfirmationDone
12+
var failUrl = ""
13+
var successUrl = ""
1214
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
1315
super.onPageStarted(view, url, favicon)
1416
val sourceURL = url.toString()
1517
if (sourceURL.isNotEmpty()&&sourceURL.contains("refNo")) {
1618
sendConfirmationResult(parseQueryParams(URL(url)))
1719
return
1820
}
21+
if (sourceURL.isNotEmpty() && successUrl.isNotEmpty() && failUrl.isNotEmpty()){
22+
if(sourceURL.contains(successUrl)||sourceURL.contains(failUrl)){
23+
sendConfirmationResult(parseQueryParams(URL(url)))
24+
return
25+
}
26+
}
1927
}
2028

2129
override fun onLoadResource(view: WebView?, url: String?) {
@@ -26,18 +34,28 @@ class PayPalWebClient(onConfirmationDone: (MutableMap<String, String>) -> Unit):
2634
super.onPageFinished(view, url)
2735
if(url!=null && url.contains("status")){
2836
sendConfirmationResult(parseQueryParams(URL(url)))
37+
return
38+
}
39+
40+
if (successUrl.isNotEmpty()&&failUrl.isNotEmpty()){
41+
if(url!=null && (url.contains(successUrl)||url.contains(failUrl))){
42+
sendConfirmationResult(parseQueryParams(URL(url)))
43+
return
44+
}
2945
}
3046
}
3147

3248
private fun parseQueryParams(urlParam: URL):MutableMap<String, String> {
3349
val queryPairs: MutableMap<String, String> = LinkedHashMap()
34-
val query: String = urlParam.query
35-
val pairs = query.split("&").toTypedArray()
36-
for (pair in pairs) {
37-
if (pair.contains("=")){
38-
val idx = pair.indexOf("=")
39-
queryPairs[URLDecoder.decode(pair.substring(0, idx), "UTF-8")] =
40-
URLDecoder.decode(pair.substring(idx + 1), "UTF-8")
50+
if (null !== urlParam?.query) {
51+
val query: String = urlParam.query
52+
val pairs = query.split("&").toTypedArray()
53+
for (pair in pairs) {
54+
if (pair.contains("=")) {
55+
val idx = pair.indexOf("=")
56+
queryPairs[URLDecoder.decode(pair.substring(0, idx), "UTF-8")] =
57+
URLDecoder.decode(pair.substring(idx + 1), "UTF-8")
58+
}
4159
}
4260
}
4361
return queryPairs

TwoCheckoutAndroidSDK/src/main/java/com/twocheckout/connectors/payments/paypal/PaypalStarter.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import androidx.appcompat.app.AppCompatActivity
88
class PaypalStarter(ctx: Context, paypalURL: String) {
99
companion object{
1010
const val keyPaypalURL = "threeds_url"
11+
const val keyPaymentSuccessURL = "success_url"
12+
const val keyPaymentFailURL = "fail_url"
1113
const val keyRefNO = "ref_no_value"
1214
const val paypalResultCode = 201
1315

@@ -21,6 +23,14 @@ class PaypalStarter(ctx: Context, paypalURL: String) {
2123
screenURL = paypalURL
2224
}
2325

26+
fun displayPaypalScreen(resultReceiver: ActivityResultLauncher<Intent>,successUrl:String,failUrl:String) {
27+
val temp = Intent(mCtx, PaypalWebScreen::class.java)
28+
temp.putExtra(keyPaypalURL,screenURL)
29+
temp.putExtra(keyPaymentSuccessURL,successUrl)
30+
temp.putExtra(keyPaymentFailURL,failUrl)
31+
resultReceiver.launch(temp)
32+
}
33+
2434
fun displayPaypalScreen(resultReceiver: ActivityResultLauncher<Intent>) {
2535
val temp = Intent(mCtx, PaypalWebScreen::class.java)
2636
temp.putExtra(keyPaypalURL,screenURL)

TwoCheckoutAndroidSDK/src/main/java/com/twocheckout/connectors/payments/paypal/PaypalWebScreen.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ internal class PaypalWebScreen:AppCompatActivity() {
2121
mPaypalWebClient = PayPalWebClient(::onPaypalFlowDone)
2222
paypalWebView.webViewClient = mPaypalWebClient
2323
paypalWebView.settings.javaScriptEnabled = true
24+
mPaypalWebClient.successUrl = intent.getStringExtra(PaypalStarter.keyPaymentSuccessURL)?:""
25+
mPaypalWebClient.failUrl = intent.getStringExtra(PaypalStarter.keyPaymentFailURL)?:""
2426
paypalWebView.loadUrl(urlToLoad)
2527
}
2628

TwoCheckoutAndroidSDK/src/main/java/com/twocheckout/connectors/screens/CardFormScreen.kt

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@ import android.graphics.Color
66
import android.graphics.drawable.ColorDrawable
77
import android.os.Bundle
88
import android.text.*
9+
import android.text.method.PasswordTransformationMethod
910
import android.view.*
1011
import android.view.View.*
1112
import android.view.inputmethod.InputMethodManager
12-
import androidx.appcompat.widget.*
13+
import androidx.appcompat.widget.AppCompatButton
14+
import androidx.appcompat.widget.AppCompatEditText
15+
import androidx.appcompat.widget.AppCompatImageView
16+
import androidx.appcompat.widget.AppCompatTextView
1317
import androidx.constraintlayout.widget.ConstraintLayout
1418
import androidx.core.content.res.ResourcesCompat
1519
import androidx.fragment.app.DialogFragment
@@ -20,12 +24,42 @@ import com.twocheckout.connectors.datapack.CreditCardInputResult
2024
import com.twocheckout.connectors.datapack.FormUICustomizationData
2125
import com.twocheckout.connectors.datapack.PayerCardData
2226
import com.twocheckout.connectors.payments.TwoCOCardValidator
27+
import java.io.Serializable
28+
29+
30+
class HideInputData : PasswordTransformationMethod() {
31+
32+
companion object {
33+
const val HIDE_CHAR = '*'
34+
}
35+
36+
override fun getTransformation(source: CharSequence, view: View): CharSequence {
37+
return DataCharSequence(source)
38+
}
39+
40+
inner class DataCharSequence (private val source: CharSequence) : CharSequence {
41+
42+
override val length: Int
43+
get() = source.length
44+
45+
override fun get(index: Int): Char = HIDE_CHAR
46+
47+
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
48+
return source.subSequence(startIndex, endIndex)
49+
}
50+
}
51+
}
2352

2453

2554
internal class CardFormScreen():DialogFragment() {
2655

56+
class CallbackActionKeeper(var onCardInput: (inputResult:CreditCardInputResult) -> Unit):
57+
Serializable
58+
59+
2760
constructor(ctx: Context,
2861
customizationParam: FormUICustomizationData,
62+
hideCard:Boolean,
2963
displayPrice: String,
3064
payButtonLabel:String,
3165
onCardInput: (inputResult:CreditCardInputResult) -> Unit
@@ -35,10 +69,12 @@ internal class CardFormScreen():DialogFragment() {
3569
price = displayPrice
3670
customizationData = customizationParam
3771
onCardInputDone = onCardInput
72+
callbackKeeper = CallbackActionKeeper(onCardInputDone)
3873
payButtonText = payButtonLabel
74+
hideCardData = hideCard
3975
}
4076

41-
77+
private var hideCardData = false
4278
private var colorCodeTitle: Int = -1
4379
private var colorCodePayBtn: Int = -1
4480
private var colorCodeHint: Int = Color.BLACK
@@ -50,6 +86,7 @@ internal class CardFormScreen():DialogFragment() {
5086
private var showFieldErrors:Boolean = false
5187
private var currentCardExpiryString = ""
5288
private var payButtonText = ""
89+
var callbackKeeper:CallbackActionKeeper? = null
5390
lateinit var cardNRHintTV:AppCompatTextView
5491
lateinit var expiryDateHintTV:AppCompatTextView
5592
lateinit var cvvNRHintTV:AppCompatTextView
@@ -137,6 +174,10 @@ internal class CardFormScreen():DialogFragment() {
137174
discoverCardTypeIM.visibility = GONE
138175
jcbCardTypeIM.visibility = GONE
139176
var showPrice = ""
177+
178+
if (hideCardData) {
179+
setHiddenCardData()
180+
}
140181
if (price.isNotEmpty()){
141182
showPrice = getString(R.string.submitPay)+" "+ price
142183
payButton.text = showPrice
@@ -585,6 +626,7 @@ internal class CardFormScreen():DialogFragment() {
585626
WindowManager.LayoutParams.MATCH_PARENT,
586627
WindowManager.LayoutParams.MATCH_PARENT
587628
)
629+
if(callbackKeeper == null) dismiss()
588630
}
589631

590632
private fun customizeForm() {
@@ -680,10 +722,14 @@ internal class CardFormScreen():DialogFragment() {
680722
outState.putString(TwoCheckoutPaymentForm.keyInputTextColor,customizationData.formInputTextColor)
681723
outState.putString(TwoCheckoutPaymentForm.keyTitleTextColor,customizationData.formTitleTextColor)
682724
outState.putString(TwoCheckoutPaymentForm.keyDisplayPrice,price)
725+
outState.putBoolean("hideCardData", hideCardData)
683726
}
684727

685728
override fun onViewStateRestored(savedInstanceState: Bundle?) {
686729
super.onViewStateRestored(savedInstanceState)
730+
if (savedInstanceState != null) {
731+
hideCardData = savedInstanceState.getBoolean("hideCardData", false)
732+
}
687733
val temp = savedInstanceState?.getInt(TwoCheckoutPaymentForm.keyTextFontID)?:-1
688734
if (temp>0) customizationData.userTextFontRes = temp
689735

@@ -727,4 +773,16 @@ internal class CardFormScreen():DialogFragment() {
727773
setupUserFontResource()
728774
customizeForm()
729775
}
776+
777+
fun setHiddenCardData() {
778+
cardNumberInput.transformationMethod = HideInputData()
779+
cvcNumberInput.transformationMethod = HideInputData()
780+
}
781+
782+
override fun onResume() {
783+
super.onResume()
784+
if (hideCardData) {
785+
setHiddenCardData()
786+
}
787+
}
730788
}

TwoCheckoutAndroidSDK/src/main/java/com/twocheckout/connectors/screens/TwoCheckoutPaymentForm.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ class TwoCheckoutPaymentForm(displayData: PaymentConfigurationData,onShowLoading
1717
val keyPayButtonColor = "payButtonColor"
1818
val keyTextFontID = "textFontResourceID"
1919
val keyDisplayPrice = "displayPrice"
20-
val keyCallbackCardInputResult = "keyCallbackCardInput"
2120

2221
fun getCardPaymentToken(merchantCode:String,cardInputObject: CreditCardInputResult,onTokenReady: (token:String) -> Unit) {
2322
val cardPayments = CardPaymentInit(onTokenReady)
@@ -43,7 +42,7 @@ class TwoCheckoutPaymentForm(displayData: PaymentConfigurationData,onShowLoading
4342
ctx as AppCompatActivity
4443
mCtx = displayData.activityContext
4544
merchantCode = displayData.merchantCodeParam
46-
cardInputScreen = CardFormScreen(ctx,displayData.displayCustomization,displayData.displayPrice,displayData.payButtonText,::onCardInputComplete)
45+
cardInputScreen = CardFormScreen(ctx,displayData.displayCustomization,displayData.hideCard,displayData.displayPrice,displayData.payButtonText,::onCardInputComplete)
4746
mFragmentManager = ctx.supportFragmentManager
4847
}
4948

app/src/main/java/com/twocheckout/mobile/CheckoutActivity.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.twocheckout.mobile
22

33

4+
import android.annotation.SuppressLint
45
import android.app.ProgressDialog
56
import android.content.Context
67
import android.content.Intent
@@ -67,6 +68,7 @@ open class CheckoutActivity : AppCompatActivity() {
6768
private val threedsReceiver = createPaymentsReceiver()
6869
private val paypalReceiver = createPaypalReceiver()
6970

71+
@SuppressLint("MissingInflatedId")
7072
override fun onCreate(savedInstanceState: Bundle?) {
7173
super.onCreate(savedInstanceState)
7274
setContentView(R.layout.activity_checkout)
@@ -212,6 +214,9 @@ open class CheckoutActivity : AppCompatActivity() {
212214
customizationParam
213215
)
214216

217+
if (SettingsActivity.getHideCardData(this)){
218+
formConfigData.hideCard = true
219+
}
215220

216221
val cardPaymentForm = TwoCheckoutPaymentForm(
217222
formConfigData,::showLoadingSpinner,
@@ -331,7 +336,7 @@ open class CheckoutActivity : AppCompatActivity() {
331336
progressDialog.dismiss()
332337
}
333338
val mPaypalStarter = PaypalStarter(this,paypalUrl)
334-
mPaypalStarter.displayPaypalScreen(paypalReceiver)
339+
mPaypalStarter.displayPaypalScreen(paypalReceiver,"www.2checkout.com","www.2checkout.com")
335340
}
336341

337342
private fun getThreedsUrl(response: String): String {

0 commit comments

Comments
 (0)