Skip to content

Commit

Permalink
[FX-800] Expose the deferPaymentCapture method (#139)
Browse files Browse the repository at this point in the history
* Implement the collectPin function

* Apply spotless

* Write most tests for new function

* Include collectPin function in sample app

* Address comments and fix return type

* Do some refactor work in VGSCollector

* Add BT collector test

* Update sample app to show pin collection for snap and cash

* Address Danilo's comments

* Change to deferCapturePayment

* Change deferCapturePayment to deferPaymentCapture

* Address Danilo's comments

* Change success text in sample app

* Remove all mention of collect pin
  • Loading branch information
dleis612 authored Dec 29, 2023
1 parent 4c95efd commit 2755533
Show file tree
Hide file tree
Showing 22 changed files with 1,137 additions and 313 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@
local.properties
SecretRingKey.gpg
/buildSrc/build/
*.jks
120 changes: 116 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [Tokenizing an EBT Card](#tokenizing-an-ebt-card)
- [Performing a balance check](#performing-a-balance-check)
- [Capturing a payment](#capturing-a-payment)
- [Submitting the PIN for a deferred payment](#submitting-the-pin-for-a-deferred-payment)
- [The ForageApiResponse sealed class](#the-forageapiresponse-sealed-class)
- [Running the Sample App](#running-the-sample-app)
- [Dependencies](#dependencies)
Expand All @@ -27,6 +28,7 @@ In addition to [UI components](#ui-components), the SDK provides APIs for:
1. [Tokenizing an EBT Card](#tokenizing-an-ebt-card)
2. [Performing a balance check](#performing-a-balance-check)
3. [Capturing a payment](#capturing-a-payment)
4. [Submitting the PIN for a deferred payment](#submitting-the-pin-for-a-deferred-payment)

Read on for installation instructions and details about the APIs.

Expand Down Expand Up @@ -504,9 +506,9 @@ class BalanceCheckViewModel @Inject constructor(

### Step 0: Create a `Payment` object

Send a POST request to Forage's `/payments/` endpoint [to create a
Send a `POST` request to Forage's `/payments/` endpoint [to create a
`Payment` object](https://docs.joinforage.app/reference/create-a-payment).
You need the `ref` of the `Payment` within the POST's response for Step 3.
You need the `ref` of the `Payment` within the `/payments` response for Step 3.

### Step 1: Add the `ForagePINEditText` UI component

Expand Down Expand Up @@ -557,7 +559,7 @@ class PaymentCaptureFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// establish bindings to ForagePANEditText
// establish bindings to ForagePINEditText
_binding = PaymentCaptureFragmentBinding.inflate(inflater, container, false)
val snapEditText = binding.snapPinEditText
val cashEditText = binding.cashPinEditText
Expand All @@ -570,7 +572,7 @@ class PaymentCaptureFragment : Fragment() {
snapEditText.setForageConfig(forageConfig)
cashEditText.setForageConfig(forageConfig)

// then freely call other methods on ForagePANEditText binding
// then freely call other methods on ForagePINEditText binding
// ...
}
}
Expand Down Expand Up @@ -623,6 +625,116 @@ class PaymentCaptureViewModel @Inject constructor(
It is the `funding_type` on a [Payment object](#step-0-create-a-payment-object)
where you indicate whether a `Payment` will capture (SNAP or EBT Cash).

## Submitting the PIN for a deferred payment

### Step 0: Create a `Payment` object

Send a `POST` request to Forage's `/payments/` endpoint [to create a
`Payment` object](https://docs.joinforage.app/reference/create-a-payment).
You need the `ref` of the `Payment` within the `/payments` response for Step 3.

### Step 1: Add the `ForagePINEditText` UI component

As with [performing a balance check](#performing-a-balance-check), you will need
to add a `ForagePINEditText` to your UI. Please reference
[Step 1 of Performing a Balance Check](#step-1-add-the-foragepinedittext-ui-component)

### Step 2: Customizing `ForagePINEditText`

Please reference [Step 2 of Performing a Balance Check](#step-2-customizing-foragepinedittext)

### Step 3: Call `setForageConfig()`

Please reference [Step 3 of Performing a Balance Check](#step-3-call-setforageconfig)

### Step 4: Submit the PIN for the deferred EBT payment

The ForageSDK provides a method to submit a customer's PIN for an EBT payment and defer the capture of the payment to the server.

```kotlin
data class DeferPaymentCaptureParams(
val foragePinEditText: ForagePINEditText,
val paymentRef: String
)

suspend fun deferPaymentCapture(
params: DeferPaymentCaptureParams
): ForageApiResponse<String>
```

#### Parameter definitions

- `DeferPaymentCaptureParams.foragePinEditText`: A reference to a `ForagePINEditText` component.
- `DeferPaymentCaptureParams.paymentRef`: The `paymentRef` parameter is a string identifier that refers to an instance in Forage's database of a [`Payment`](https://docs.joinforage.app/reference/create-a-payment)

#### Example

```kotlin
@AndroidEntryPoint
class DeferPaymentCaptureFragment : Fragment() {
private val viewModel: DeferPaymentCaptureViewModel by viewModels()
private var _binding: DeferPaymentCaptureFragmentBinding? = null
private val binding get() = _binding!!

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// establish bindings to ForagePINEditText
_binding = DeferPaymentCaptureFragmentBinding.inflate(inflater, container, false)
val snapEditText = binding.snapPinEditText
val cashEditText = binding.cashPinEditText

// immediately call setForageConfig() on the binding
val forageConfig = ForageConfig(
merchantId = viewModel.merchantAccount,
sessionToken = viewModel.bearer
)
snapEditText.setForageConfig(forageConfig)
cashEditText.setForageConfig(forageConfig)

// then freely call other methods on ForagePINEditText binding
// ...
}
}
```

```kotlin
@HiltViewModel
class DeferPaymentCaptureViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val moshi: Moshi
) : ViewModel() {
private val args = FlowCapturePaymentFragmentArgs.fromSavedStateHandle(savedStateHandle)

// internal so that DeferPaymentCaptureFragment can access these values
val snapPaymentRef = args.snapPaymentRef
val cashPaymentRef = args.cashPaymentRef
val merchantID = args.merchantID
val sessionToken = args.sessionToken

fun deferPaymentCapture(pinForageEditText: ForagePINEditText, paymentRef: String) =
viewModelScope.launch {
val response = ForageSDK().deferPaymentCapture(
DeferPaymentCaptureParams(
foragePinEditText = pinForageEditText,
paymentRef = paymentRef
)
)

when (response) {
is ForageApiResponse.Success -> {
// handle successful reponse
}
is ForageApiResponse.Failure -> {
// handle error response
}
}
}
}
```

## The ForageApiResponse sealed class

The SDK provides suspending functions to interact with the Forage API.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.joinforage.forage.android.network.PaymentService
import com.joinforage.forage.android.network.TokenizeCardService
import com.joinforage.forage.android.network.data.CapturePaymentRepository
import com.joinforage.forage.android.network.data.CheckBalanceRepository
import com.joinforage.forage.android.network.data.DeferPaymentCaptureRepository
import com.joinforage.forage.android.network.model.ForageApiResponse
import com.joinforage.forage.android.ui.AbstractForageElement
import com.joinforage.forage.android.ui.ForageConfig
Expand Down Expand Up @@ -267,4 +268,68 @@ class ForageSDK : ForageSDKInterface {

return response
}

/**
* Capture a customer's PIN for an EBT payment and defer the capture of the payment to the server
*
* @param params The parameters required for pin capture, including
* reference to a ForagePINEditText and a Payment ref
*
* @return A ForageAPIResponse indicating the success or failure of the
* PIN capture. On success, returns `Nothing`.
* On failure, provides a detailed error response.
*
* @throws ForageConfigNotSetException If the passed ForagePINEditText instance
* hasn't had its ForageConfig set via .setForageConfig().
*/
override suspend fun deferPaymentCapture(params: DeferPaymentCaptureParams): ForageApiResponse<String> {
val (foragePinEditText, paymentRef) = params
val (merchantId, sessionToken) = _getForageConfigOrThrow(foragePinEditText)
val config = EnvConfig.fromSessionToken(sessionToken)

// TODO: replace Log.getInstance() with Log() in future PR
val logger = Log.getInstance()
logger.i(
"[HTTP] Submitting defer capture request for Payment $paymentRef",
attributes = mapOf(
"merchant_ref" to merchantId,
"payment_ref" to paymentRef
)
)

return DeferPaymentCaptureRepository(
pinCollector = foragePinEditText.getCollector(
merchantId
),
encryptionKeyService = EncryptionKeyService(
okHttpClient = OkHttpClientBuilder.provideOkHttpClient(
sessionToken,
merchantId,
traceId = logger.getTraceIdValue()
),
httpUrl = config.baseUrl,
logger = logger
),
paymentService = PaymentService(
okHttpClient = OkHttpClientBuilder.provideOkHttpClient(
sessionToken,
merchantId,
traceId = logger.getTraceIdValue()
),
httpUrl = config.baseUrl,
logger = logger
),
paymentMethodService = PaymentMethodService(
okHttpClient = OkHttpClientBuilder.provideOkHttpClient(
sessionToken,
merchantId,
traceId = logger.getTraceIdValue()
),
httpUrl = config.baseUrl,
logger = logger
)
).deferPaymentCapture(
paymentRef = paymentRef
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ internal interface ForageSDKInterface {
* On success, returns an object with `snap` and `cash` fields, whose values
* indicate the balance of each tender as of now
*
* @throws ForageConfigNotSetException If the passed ForagePANEditText instance
* @throws ForageConfigNotSetException If the passed ForagePINEditText instance
* hasn't had its ForageConfig set via .setForageConfig().
*/
suspend fun checkBalance(params: CheckBalanceParams): ForageApiResponse<String>
Expand All @@ -62,10 +62,25 @@ internal interface ForageSDKInterface {
* payment capture. On success, returns a confirmation of the transaction.
* On failure, provides a detailed error response.
*
* @throws ForageConfigNotSetException If the passed ForagePANEditText instance
* @throws ForageConfigNotSetException If the passed ForagePINEditText instance
* hasn't had its ForageConfig set via .setForageConfig().
*/
suspend fun capturePayment(params: CapturePaymentParams): ForageApiResponse<String>

/**
* Capture a customer's PIN for an EBT payment and defer the capture of the payment to the server
*
* @param params The parameters required for pin capture, including
* reference to a ForagePINEditText and a Payment ref
*
* @return A ForageAPIResponse indicating the success or failure of the
* PIN capture. On success, returns `Nothing`.
* On failure, provides a detailed error response.
*
* @throws ForageConfigNotSetException If the passed ForagePINEditText instance
* hasn't had its ForageConfig set via .setForageConfig().
*/
suspend fun deferPaymentCapture(params: DeferPaymentCaptureParams): ForageApiResponse<String>
}

/**
Expand Down Expand Up @@ -108,3 +123,16 @@ data class CapturePaymentParams(
val foragePinEditText: ForagePINEditText,
val paymentRef: String
)

/**
* Data class representing the parameters required for capturing the PIN for a deferred EBT payment.
* The EBT payment will then be confirmed through a request from the server.
*
* @property foragePinEditText A UI ForagePINEditText UI component. Importantly,
* you must have called .setForageConfig() already
* @property paymentRef A reference to the intended payment transaction.
*/
data class DeferPaymentCaptureParams(
val foragePinEditText: ForagePINEditText,
val paymentRef: String
)
Loading

0 comments on commit 2755533

Please sign in to comment.