Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor example app build config keys #1505

Merged
merged 8 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ If you are upgrading from 4.x.x to a current release, check out our [migration g
If you use ProGuard or R8, you do not need to manually add any rules, as they are automatically embedded in the artifacts.
Please let us know if you find any issues.

## Development

For development and testing purposes the project is accompanied by a test app. See [here](example-app/README.md) how to set it up and run it.

## Support

If you have a feature request, or spotted a bug or a technical problem, [create an issue here][github.newIssue].
Expand Down
27 changes: 27 additions & 0 deletions example-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Example app

jreij marked this conversation as resolved.
Show resolved Hide resolved
The `example-app` module is used for development and testing purposes. It should not be used as a template for your own integration. Check out the [docs](https://docs.adyen.com/online-payments/build-your-integration/) for best practices on integration.

## Running the app

Steps to run the example app:
1. Build a server that acts as a proxy between the app and the Adyen Checkout API.
* Your server should mirror the necessary endpoints for your flow (for example [/sessions](https://docs.adyen.com/api-explorer/Checkout/latest/post/sessions) for the sessions flow and [/paymentMethods](https://docs.adyen.com/api-explorer/Checkout/latest/post/paymentMethods), [/payments](https://docs.adyen.com/api-explorer/Checkout/latest/post/payments) and [/payments/details](https://docs.adyen.com/api-explorer/Checkout/latest/post/payments/details) for the advanced flow).
* The API key should be managed on the server.
2. Duplicate `example.local.gradle` and name it `local.gradle`. Make sure the file is placed in the `example-app` directory.
3. Replace the predefined values:
* `MERCHANT_SERVER_URL`: the URL to your server.
* `CLIENT_KEY`: your client key. Find out how to obtain it [here](https://docs.adyen.com/development-resources/client-side-authentication/#get-your-client-key).
* `MERCHANT_ACCOUNT`: your merchant account identifier.
* `AUTHORIZATION_HEADER_NAME`: the name of the authorization header as expected by your server. You can use an empty string if this is not applicable for you.
* `AUTHORIZATION_HEADER_VALUE`: the value for the authorization header. You can use an empty string if this is not applicable for you.
4. Sync the project.
5. Run on any device or emulator.

> [!WARNING]
> In case you don't have your own server you can connect to the Adyen Checkout API directly for testing purposes only. Be aware this could potentially leak your credentials, the market-ready application must never connect to Adyen API directly.

To connect to the Adyen Checkout API directly you can use the following values:
* `MERCHANT_SERVER_URL`: `https://checkout-test.adyen.com/{VERSION}/` (check [here](https://docs.adyen.com/api-explorer/Checkout/latest/overview) for the latest version).
* `AUTHORIZATION_HEADER_NAME`: `x-api-key`.
* `AUTHORIZATION_HEADER_VALUE`: your API key.
6 changes: 4 additions & 2 deletions example-app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ apply from: "${rootDir}/config/gradle/ci.gradle"

if (file("local.gradle").exists()) {
apply from: "local.gradle"
} else if (System.getenv('CI')) {
// if building from CI use example file as it is to ensure the build passes
apply from: "example.local.gradle"
} else {
logger.lifecycle("File example-app/local.gradle not found. Falling back to default file with no values.")
apply from: "default.local.gradle"
throw new GradleException("File example-app/local.gradle not found. Check example-app/README.md for more instructions.")
}

// This runConnectedAndroidTest.gradle script is applied,
Expand Down
23 changes: 0 additions & 23 deletions example-app/default.local.gradle

This file was deleted.

22 changes: 22 additions & 0 deletions example-app/example.local.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Duplicate this file into "local.gradle" then replace the placeholder values with the correct
* parameters. You might need to escape some characters if you see an error.
*
* DO NOT commit the new file anywhere public, you might be exposing your secret credentials.
*/
android {
buildTypes {
debug {
buildConfigField "String", "MERCHANT_SERVER_URL", '"YOUR_SERVER_URL"'
buildConfigField "String", "CLIENT_KEY", '"YOUR_CLIENT_KEY"'
buildConfigField "String", "MERCHANT_ACCOUNT", '"YOUR_MERCHANT_ACCOUNT"'
buildConfigField "String", "AUTHORIZATION_HEADER_NAME", '"YOUR_AUTHORIZATION_HEADER_NAME"'
buildConfigField "String", "AUTHORIZATION_HEADER_VALUE", '"YOUR_AUTHORIZATION_HEADER_VALUE"'
}

release {
initWith debug
matchingFallbacks = ['debug']
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
package com.adyen.checkout.example.data.api

import com.adyen.checkout.components.core.PaymentMethodsApiResponse
import com.adyen.checkout.example.BuildConfig
import com.adyen.checkout.example.data.api.model.BalanceRequest
import com.adyen.checkout.example.data.api.model.CancelOrderRequest
import com.adyen.checkout.example.data.api.model.CreateOrderRequest
Expand All @@ -27,14 +26,6 @@ import retrofit2.http.Query

internal interface CheckoutApiService {

companion object {
private const val DEFAULT_GRADLE_SERVER_URL = "<YOUR_SERVER_URL>"

fun isRealUrlAvailable(): Boolean {
return BuildConfig.MERCHANT_SERVER_URL != DEFAULT_GRADLE_SERVER_URL
}
}

@POST("sessions")
suspend fun sessionsAsync(@Body sessionRequest: SessionRequest): SessionModel

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ internal class DefaultKeyValueStorage(
return sharedPreferences.getString(
appContext = appContext,
stringRes = R.string.shopper_reference_key,
defaultValue = BuildConfig.SHOPPER_REFERENCE,
defaultStringRes = R.string.preferences_default_shopper_reference,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,29 +29,30 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
object NetworkModule {

private val BASE_URL = if (CheckoutApiService.isRealUrlAvailable()) {
BuildConfig.MERCHANT_SERVER_URL
} else {
"http://myserver.com/my/endpoint/"
}

@Singleton
@Provides
internal fun provideOkHttpClient(): OkHttpClient {
val builder = OkHttpClient.Builder()

val authorizationHeader = (BuildConfig.AUTHORIZATION_HEADER_NAME to BuildConfig.AUTHORIZATION_HEADER_VALUE)
.takeIf { it.first.isNotBlank() }

if (BuildConfig.DEBUG) {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val interceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
if (authorizationHeader != null) redactHeader(authorizationHeader.first)
}
builder.addNetworkInterceptor(interceptor)
}

builder.addInterceptor { chain ->
val request = chain.request().newBuilder()
.addHeader(BuildConfig.API_KEY_HEADER_NAME, BuildConfig.CHECKOUT_API_KEY)
.build()
if (authorizationHeader != null) {
builder.addInterceptor { chain ->
val request = chain.request().newBuilder()
.header(authorizationHeader.first, authorizationHeader.second)
.build()

chain.proceed(request)
chain.proceed(request)
}
}

return builder.build()
Expand All @@ -63,7 +64,7 @@ object NetworkModule {
Moshi.Builder()
.add(JSONObjectAdapter())
.add(KotlinJsonAdapterFactory())
.build()
.build(),
)

@Singleton
Expand All @@ -74,7 +75,7 @@ object NetworkModule {
converterFactory: Converter.Factory,
): Retrofit =
Retrofit.Builder()
.baseUrl(BASE_URL)
.baseUrl(BuildConfig.MERCHANT_SERVER_URL)
.client(okHttpClient)
.addConverterFactory(converterFactory)
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.DropDownPreference
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceFragmentCompat
import com.adyen.checkout.example.R
import com.adyen.checkout.example.data.storage.KeyValueStorage
import com.adyen.checkout.example.databinding.ActivitySettingsBinding
import com.adyen.checkout.example.ui.theme.NightTheme
import com.adyen.checkout.example.ui.theme.NightThemeRepository
Expand All @@ -39,6 +41,9 @@ class ConfigurationActivity : AppCompatActivity() {
@AndroidEntryPoint
class ConfigurationFragment : PreferenceFragmentCompat() {

@Inject
lateinit var keyValueStorage: KeyValueStorage

@Inject
internal lateinit var nightThemeRepository: NightThemeRepository

Expand All @@ -49,12 +54,19 @@ class ConfigurationActivity : AppCompatActivity() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

preferenceManager.preferenceScreen
.findPreference<DropDownPreference>(requireContext().getString(R.string.night_theme_title))
preferenceManager
.findPreference<DropDownPreference>(requireContext().getString(R.string.night_theme_key))
?.setOnPreferenceChangeListener { _, newValue ->
nightThemeRepository.theme = NightTheme.findByPreferenceValue(newValue as String?)
true
}

/* This workaround is needed to display the default value of Merchant Account. We cannot set this value in
`preferences.xml` because it's only available in the code and there is no "clean" way to set the default
value programmatically. */
araratthehero marked this conversation as resolved.
Show resolved Hide resolved
preferenceManager
.findPreference<EditTextPreference>(requireContext().getString(R.string.merchant_account_key))
?.text = keyValueStorage.getMerchantAccount()
}
}
}
1 change: 1 addition & 0 deletions example-app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,6 @@
<string name="preferences_default_instant_payment_method">wechatpaySDK</string>
<string name="preferences_default_use_sessions">true</string>
<string name="preferences_default_analytics_level">ALL</string>
<string name="preferences_default_shopper_reference">test-android-components</string>

</resources>
3 changes: 2 additions & 1 deletion example-app/src/main/res/xml/preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
<PreferenceCategory android:title="@string/shopper_information">

<EditTextPreference
android:defaultValue="@string/preferences_default_shopper_reference"
android:key="@string/shopper_reference_key"
android:title="@string/shopper_reference_title"
app:useSimpleSummaryProvider="true" />
Expand Down Expand Up @@ -130,7 +131,7 @@
android:defaultValue="@string/night_theme_system"
android:entries="@array/night_theme_entries"
android:entryValues="@array/night_theme_values"
android:key="@string/night_theme_title"
android:key="@string/night_theme_key"
android:title="@string/night_theme_title"
app:useSimpleSummaryProvider="true" />

Expand Down
Loading