Skip to content
Snippets Groups Projects
PaymentManager.kt 5.34 KiB
Newer Older
Koen's avatar
Koen committed
package com.futo.futopay

abb128's avatar
abb128 committed
import android.content.Context
Koen's avatar
Koen committed
import android.util.Log
import android.view.ViewGroup
abb128's avatar
abb128 committed
import androidx.activity.ComponentActivity
Koen's avatar
Koen committed
import androidx.fragment.app.Fragment
abb128's avatar
abb128 committed
import androidx.lifecycle.LifecycleCoroutineScope
Koen's avatar
Koen committed
import androidx.lifecycle.lifecycleScope
import com.stripe.android.PaymentConfiguration
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.PaymentSheetResult
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.net.URL

abb128's avatar
abb128 committed
interface PaymentStatusListener {
abb128's avatar
abb128 committed
    fun onSuccess(purchaseId: String?);
abb128's avatar
abb128 committed
    fun onCancel();
    fun onFailure(error: Throwable);
}
Koen's avatar
Koen committed

class PaymentManager {
    private val _fragment: Fragment;
    private val _overlayContainer: ViewGroup;
    private val _sheet: PaymentSheet;
    private val _paymentState: PaymentState;
abb128's avatar
abb128 committed
    private val _listener: PaymentStatusListener;
Koen's avatar
Koen committed

Kelvin's avatar
Kelvin committed
    private var _lastPurchaseId: String? = null;

abb128's avatar
abb128 committed
    constructor(paymentState: PaymentState, fragment: Fragment, overlayContainer: ViewGroup, listener: PaymentStatusListener) {
Koen's avatar
Koen committed
        _fragment = fragment;
        _paymentState = paymentState;
        _overlayContainer = overlayContainer;
abb128's avatar
abb128 committed
        _listener = listener;
Koen's avatar
Koen committed

        _sheet = PaymentSheet(_fragment) { paymentSheetResult ->
            when(paymentSheetResult) {
                is PaymentSheetResult.Canceled -> {
abb128's avatar
abb128 committed
                    _listener.onCancel();
Koen's avatar
Koen committed
                }
                is PaymentSheetResult.Failed -> {
abb128's avatar
abb128 committed
                    _listener.onFailure(paymentSheetResult.error);
Koen's avatar
Koen committed
                }
                is PaymentSheetResult.Completed -> {
abb128's avatar
abb128 committed
                    _listener.onSuccess(_lastPurchaseId);
Koen's avatar
Koen committed
                }
            }
        };
    }

    fun startPayment(paymentState: PaymentState, scope: CoroutineScope, productId: String) {
        scope.launch(Dispatchers.IO){
            try{
                val availableCurrencies = _paymentState.getAvailableCurrencies(productId);
Kelvin's avatar
Kelvin committed
                val country = paymentState.getPaymentCountryFromIP()?.let { c -> PaymentConfigurations.COUNTRIES.find { it.id.equals(c, ignoreCase = true) } };
Koen's avatar
Koen committed
                withContext(Dispatchers.Main) {
abb128's avatar
abb128 committed
                    SlideUpPayment.startPayment(paymentState, _overlayContainer, productId, country, availableCurrencies, { method, request ->
Koen's avatar
Koen committed
                        when(method) {
                            "stripe" -> startPaymentStripe(productId, request.currency, request.mail, request.country, request.zipcode);
                        }
abb128's avatar
abb128 committed
                    }, { _listener.onCancel() });
Koen's avatar
Koen committed
                }
            }
            catch(ex: Throwable) {
                Log.e(TAG, "startPayment failed", ex);
                scope.launch(Dispatchers.Main){
                    UIDialogs.showGeneralErrorDialog(_fragment.requireContext(), "Failed to get required payment data (did you grant the app network permission?)", ex, onOk = { _listener.onCancel() });
Koen's avatar
Koen committed
                }
            }
        }
    }

    private fun startPaymentStripe(productId: String, currency: String, email: String, country: String? = null, zipcode: String? = null) {
        _fragment.lifecycleScope.launch(Dispatchers.IO) {
            try {
                Log.i("BuyFragment", "Starting payment");
                val paymentIntentResult = _paymentState.getPaymentIntent(productId, currency, email, country, zipcode);
                val customerConfig = if(paymentIntentResult.customer != null && paymentIntentResult.ephemeralKey != null)
                    PaymentSheet.CustomerConfiguration(paymentIntentResult.customer, paymentIntentResult.ephemeralKey);
                else null;

Kelvin's avatar
Kelvin committed
                _lastPurchaseId = paymentIntentResult.purchaseId;
Koen's avatar
Koen committed

                PaymentConfiguration.init(_fragment.requireContext(), paymentIntentResult.publishableKey);

                withContext(Dispatchers.Main) {
                    _sheet.presentWithPaymentIntent(
                        paymentIntentResult.paymentIntent, PaymentSheet.Configuration(
                            merchantDisplayName = "FUTO",
                            customer = customerConfig,
                            allowsDelayedPaymentMethods = true,
                            defaultBillingDetails = PaymentSheet.BillingDetails(PaymentSheet.Address(country = country, postalCode = zipcode), email),
                            billingDetailsCollectionConfiguration = PaymentSheet.BillingDetailsCollectionConfiguration(
                                email = PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode.Always
                            )
                        )
                    );
                }
            }
            catch(ex: Throwable) {
                Log.e(TAG, "Payment failed: ${ex.message}", ex);
                withContext(Dispatchers.Main) {
                    UIDialogs.showGeneralErrorDialog(_fragment.requireContext(), "Payment failed\nIf you are charged you should always receive the key in your mail.", ex);
                }
            }
        }
    }

    //TODO: Determine a good provider

    data class PaymentRequest(
        val productId: String,
        val currency: String,
        val mail: String,
        val country: String? = null,
        val zipcode: String? = null
    );

    companion object {
        private const val TAG = "PaymentManager"
    }
}