Skip to content

Commit

Permalink
Drop all pos related code from public ecom repo
Browse files Browse the repository at this point in the history
We want the private pos repo and the public ecom repo to have the
ability to evolve separately accordingly their unique needs while
still sharing common code. The first step in this journey is to
make the public ecom repo completely unaware of the existence of
the private pos repo. We do that by deleting all of the pos code

Signed-off-by: Devin Morgan <[email protected]>
  • Loading branch information
devinmorgan committed May 9, 2024
1 parent 0f69837 commit be479a9
Show file tree
Hide file tree
Showing 91 changed files with 227 additions and 7,481 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ import com.joinforage.forage.android.network.data.CheckBalanceRepository
import com.joinforage.forage.android.network.data.DeferPaymentCaptureRepository
import com.joinforage.forage.android.network.data.DeferPaymentRefundRepository
import com.joinforage.forage.android.network.model.ForageApiResponse
import com.joinforage.forage.android.pos.PosRefundPaymentRepository
import com.joinforage.forage.android.pos.PosRefundService
import com.joinforage.forage.android.ui.AbstractForageElement
import com.joinforage.forage.android.ui.ForageConfig
import com.joinforage.forage.android.ui.ForagePINEditText
Expand Down Expand Up @@ -460,7 +458,6 @@ class ForageSDK : ForageSDKInterface {
private val paymentService by lazy { createPaymentService() }
private val messageStatusService by lazy { createMessageStatusService() }
private val pollingService by lazy { createPollingService() }
private val posRefundService by lazy { PosRefundService(config.apiBaseUrl, logger, okHttpClient) }

open fun createTokenizeCardService() = TokenizeCardService(
config.apiBaseUrl,
Expand Down Expand Up @@ -507,18 +504,6 @@ class ForageSDK : ForageSDKInterface {
)
}

open fun createRefundPaymentRepository(foragePinEditText: ForagePINEditText): PosRefundPaymentRepository {
return PosRefundPaymentRepository(
vaultSubmitter = createVaultSubmitter(foragePinEditText),
encryptionKeyService = encryptionKeyService,
paymentMethodService = paymentMethodService,
paymentService = paymentService,
pollingService = pollingService,
logger = logger,
refundService = posRefundService
)
}

private fun createVaultSubmitter(foragePinEditText: ForagePINEditText) = AbstractVaultSubmitter.create(
foragePinEditText = foragePinEditText,
logger = logger
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.joinforage.forage.android

import android.content.res.TypedArray
import okhttp3.HttpUrl
import org.json.JSONObject
import kotlin.random.Random

/**
Expand All @@ -24,28 +23,3 @@ internal fun TypedArray.getBoxCornerRadius(styleIndex: Int, defaultBoxCornerRadi
val styledBoxCornerRadius = getDimension(styleIndex, 0f)
return if (styledBoxCornerRadius == 0f) defaultBoxCornerRadius else styledBoxCornerRadius
}

// This extension splits the path by "/" and adds each segment individually to the path.
// This is to prevent the URL from getting corrupted through internal OKHttp URL encoding.
internal fun HttpUrl.Builder.addPathSegmentsSafe(path: String): HttpUrl.Builder {
path.split("/").forEach { segment ->
if (segment.isNotEmpty()) {
this.addPathSegment(segment)
}
}
return this
}

/**
* [JSONObject.optString] has trouble falling back to `null` and seems to fallback to `"null"` (string) instead
*/
internal fun JSONObject.getStringOrNull(fieldName: String): String? {
if (!has(fieldName) || isNull(fieldName)) {
return null
}
return optString(fieldName)
}

internal fun JSONObject.hasNonNull(fieldName: String): Boolean {
return has(fieldName) && !isNull(fieldName)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
package com.joinforage.forage.android.vault

import android.content.Context
import com.joinforage.forage.android.VaultType
import com.joinforage.forage.android.core.telemetry.Log
import com.joinforage.forage.android.core.telemetry.UserAction
import com.joinforage.forage.android.core.telemetry.VaultProxyResponseMonitor
import com.joinforage.forage.android.model.EncryptionKeys
import com.joinforage.forage.android.model.PaymentMethod
import com.joinforage.forage.android.network.ForageConstants
import com.joinforage.forage.android.network.model.ForageApiResponse
import com.joinforage.forage.android.network.model.ForageError
import com.joinforage.forage.android.network.model.UnknownErrorApiResponse
import com.joinforage.forage.android.ui.ForagePINEditText

internal val IncompletePinError = ForageApiResponse.Failure.fromError(
ForageError(400, "user_error", "Invalid EBT Card PIN entered. Please enter your 4-digit PIN.")
)

internal open class VaultSubmitterParams(
open val encryptionKeys: EncryptionKeys,
open val idempotencyKey: String,
open val merchantId: String,
open val path: String,
open val paymentMethod: PaymentMethod,
open val sessionToken: String,
open val userAction: UserAction
)

internal interface VaultSubmitter {
suspend fun submit(params: VaultSubmitterParams): ForageApiResponse<String>

fun getVaultType(): VaultType
}

internal abstract class AbstractVaultSubmitter<VaultResponse>(
protected val context: Context,
protected val foragePinEditText: ForagePINEditText,
protected val logger: Log,
private val vaultType: VaultType
) : VaultSubmitter {
// interface methods
override suspend fun submit(params: VaultSubmitterParams): ForageApiResponse<String> {
logger.addAttribute("payment_method_ref", params.paymentMethod.ref)
.addAttribute("merchant_ref", params.merchantId)
logger.i("[$vaultType] Sending ${params.userAction} request to $vaultType")

// If the PIN isn't valid (less than 4 numbers) then return a response here.
if (!foragePinEditText.getElementState().isComplete) {
logger.w("[$vaultType] User attempted to submit an incomplete PIN")
return IncompletePinError
}

val vaultToken = getVaultToken(params.paymentMethod)
val encryptionKey = parseEncryptionKey(params.encryptionKeys)

// if a vault provider is missing a token, we will
// gracefully fail here
if (vaultToken.isNullOrEmpty()) {
logger.e("Vault token is missing from Payments API response")
return UnknownErrorApiResponse
}

// ========= USED FOR REPORTING IMPORTANT METRICS =========
val proxyResponseMonitor = VaultProxyResponseMonitor(
vault = vaultType,
userAction = params.userAction,
metricsLogger = logger
)
proxyResponseMonitor
.setPath(params.path)
.setMethod("POST")
.start()
// ==========================================================

val vaultProxyRequest = buildProxyRequest(
params = params,
encryptionKey = encryptionKey,
vaultToken = vaultToken
).setPath(params.path).setParams(params)

val forageResponse = submitProxyRequest(vaultProxyRequest)
proxyResponseMonitor.end()

// FNS requirement to clear the PIN after each submission
foragePinEditText.clearText()

if (forageResponse is ForageApiResponse.Failure && forageResponse.errors.isNotEmpty()) {
val forageError = forageResponse.errors.first()
proxyResponseMonitor.setForageErrorCode(forageError.code)
proxyResponseMonitor.setHttpStatusCode(forageError.httpStatusCode)
} else {
proxyResponseMonitor.setHttpStatusCode(200)
}
proxyResponseMonitor.logResult()

return forageResponse
}

override fun getVaultType(): VaultType {
return vaultType
}

// abstract methods
internal abstract fun parseEncryptionKey(encryptionKeys: EncryptionKeys): String
internal abstract suspend fun submitProxyRequest(vaultProxyRequest: VaultProxyRequest): ForageApiResponse<String>
internal abstract fun getVaultToken(paymentMethod: PaymentMethod): String?

/**
* @return [UnknownErrorApiResponse] if the response is a vault error, or null if it is not
*/
internal abstract fun toVaultErrorOrNull(vaultResponse: VaultResponse): ForageApiResponse.Failure?
internal abstract fun toForageErrorOrNull(vaultResponse: VaultResponse): ForageApiResponse.Failure?
internal abstract fun toForageSuccessOrNull(vaultResponse: VaultResponse): ForageApiResponse.Success<String>?

/**
* @return A string containing the raw error details of the vault error.
* To be used for internal error reporting.
*/
internal abstract fun parseVaultErrorMessage(vaultResponse: VaultResponse): String

// concrete methods
protected open fun buildProxyRequest(
params: VaultSubmitterParams,
encryptionKey: String,
vaultToken: String
) = VaultProxyRequest.emptyRequest()
.setHeader(ForageConstants.Headers.X_KEY, encryptionKey)
.setHeader(ForageConstants.Headers.MERCHANT_ACCOUNT, params.merchantId)
.setHeader(ForageConstants.Headers.IDEMPOTENCY_KEY, params.idempotencyKey)
.setHeader(ForageConstants.Headers.TRACE_ID, logger.getTraceIdValue())
.setHeader(ForageConstants.Headers.API_VERSION, "default")
.setToken(vaultToken)

// PaymentMethod.card.token is in the comma-separated format <vgs-token>,<basis-theory-token>,<forage-token>
protected fun pickVaultTokenByIndex(paymentMethod: PaymentMethod, index: Int): String? {
val tokensString = paymentMethod.card.token
val tokensList = tokensString.split(TOKEN_DELIMITER)

val noTokenStoredInVault = tokensList.size <= index
if (noTokenStoredInVault) return null

return tokensList[index]
}

protected fun vaultToForageResponse(vaultResponse: VaultResponse): ForageApiResponse<String> {
if (vaultResponse == null) {
logger.e("[$vaultType] Received null response from $vaultType")
return UnknownErrorApiResponse
}

val vaultError = toVaultErrorOrNull(vaultResponse)
if (vaultError != null) {
val rawVaultError = parseVaultErrorMessage(vaultResponse)
logger.e("[$vaultType] Received error from $vaultType: $rawVaultError")
return vaultError
}

val forageApiErrorResponse = toForageErrorOrNull(vaultResponse)
if (forageApiErrorResponse != null) {
val firstError = forageApiErrorResponse.errors[0]
logger.e("[$vaultType] Received ForageError from $vaultType: $firstError")
return forageApiErrorResponse
}

val forageApiSuccess = toForageSuccessOrNull(vaultResponse)
if (forageApiSuccess != null) {
logger.i("[$vaultType] Received successful response from $vaultType")
return forageApiSuccess
}
logger.e("[$vaultType] Received malformed response from $vaultType: $vaultResponse")

return UnknownErrorApiResponse
}

protected fun buildBaseRequestBody(vaultProxyRequest: VaultProxyRequest): HashMap<String, Any> {
return hashMapOf(
ForageConstants.RequestBody.CARD_NUMBER_TOKEN to vaultProxyRequest.vaultToken
)
}

internal companion object {
internal fun create(foragePinEditText: ForagePINEditText, logger: Log): VaultSubmitter {
val vaultType = foragePinEditText.getVaultType()
if (vaultType == VaultType.BT_VAULT_TYPE) {
return BasisTheoryPinSubmitter(
context = foragePinEditText.context,
foragePinEditText = foragePinEditText,
logger = logger
)
}
return VgsPinSubmitter(
context = foragePinEditText.context,
foragePinEditText = foragePinEditText,
logger = logger
)
}

const val TOKEN_DELIMITER = ","

internal fun balancePath(paymentMethodRef: String) =
"/api/payment_methods/$paymentMethodRef/balance/"

internal fun capturePaymentPath(paymentRef: String) =
"/api/payments/$paymentRef/capture/"

internal fun deferPaymentCapturePath(paymentRef: String) =
"/api/payments/$paymentRef/collect_pin/"

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import com.joinforage.forage.android.network.model.ForageApiResponse
import com.joinforage.forage.android.network.model.ForageError
import com.joinforage.forage.android.network.model.PaymentMethodRequestBody
import com.joinforage.forage.android.network.model.RequestBody
import com.joinforage.forage.android.pos.PosPaymentMethodRequestBody
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.MediaType.Companion.toMediaTypeOrNull
Expand Down Expand Up @@ -39,19 +38,6 @@ internal class TokenizeCardService(
ForageApiResponse.Failure(listOf(ForageError(500, "unknown_server_error", ex.message.orEmpty())))
}

suspend fun tokenizePosCard(track2Data: String, reusable: Boolean = true): ForageApiResponse<String> = try {
logger.i("[POS] POST request for Payment Method with Track 2 data")
tokenizeCardCoroutine(
PosPaymentMethodRequestBody(
track2Data = track2Data,
reusable = reusable
)
)
} catch (ex: IOException) {
logger.e("[POS] Failed while tokenizing PaymentMethod", ex)
ForageApiResponse.Failure(listOf(ForageError(500, "unknown_server_error", ex.message.orEmpty())))
}

private suspend fun tokenizeCardCoroutine(requestBody: RequestBody): ForageApiResponse<String> {
val url = getTokenizeCardUrl()
val okHttpRequestBody = requestBody
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import com.joinforage.forage.android.network.PollingService
import com.joinforage.forage.android.network.model.ForageApiResponse
import com.joinforage.forage.android.network.model.Message
import com.joinforage.forage.android.network.model.PaymentMethod
import com.joinforage.forage.android.pos.PosBalanceVaultSubmitterParams
import com.joinforage.forage.android.vault.AbstractVaultSubmitter
import com.joinforage.forage.android.vault.VaultSubmitter
import com.joinforage.forage.android.vault.VaultSubmitterParams
Expand Down Expand Up @@ -71,30 +70,6 @@ internal class CheckBalanceRepository(
}
}

suspend fun posCheckBalance(
merchantId: String,
paymentMethodRef: String,
posTerminalId: String,
sessionToken: String
): ForageApiResponse<String> {
return checkBalance(
merchantId = merchantId,
paymentMethodRef = paymentMethodRef,
sessionToken = sessionToken,
getVaultRequestParams = { encryptionKeys, paymentMethod ->
PosBalanceVaultSubmitterParams(
baseVaultSubmitterParams = buildVaultRequestParams(
merchantId = merchantId,
encryptionKeys = encryptionKeys,
paymentMethod = paymentMethod,
sessionToken = sessionToken
),
posTerminalId = posTerminalId
)
}
)
}

private fun buildVaultRequestParams(
merchantId: String,
encryptionKeys: EncryptionKeys,
Expand Down
Loading

0 comments on commit be479a9

Please sign in to comment.