Skip to content

Commit

Permalink
Offer purchase and Base Plan purchase conflict issue resolved
Browse files Browse the repository at this point in the history
Billing client release issue resolved
Bugs resolved
Introduced currency symbol in product info
  • Loading branch information
hannanshahid committed Dec 17, 2024
1 parent 316cae1 commit b7a7d91
Show file tree
Hide file tree
Showing 12 changed files with 420 additions and 252 deletions.
8 changes: 8 additions & 0 deletions .idea/deploymentTargetSelector.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/other.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Add Funsol Billing Helper dependencies in App level build.gradle.
```kotlin

dependencies {
implementation 'com.github.Funsol-Projects:Funsol-Billing-Helper:v2.0.1'
implementation 'com.github.Funsol-Projects:Funsol-Billing-Helper:v2.0.2'
}

```
Expand Down Expand Up @@ -455,6 +455,11 @@ This Method used for Releasing the client object and save from memory leaks
- Billing client ready call back issue resolved **(Now Exactly call after billing all setup finish)**
- Code optimized
- Bugs solved
- 17-12-2024
- Offer purchase and Base Plan purchase conflict issue resolved
- Billing client release issue resolved
- Bugs resolved
- Introduced currency symbol in product info

## License

Expand Down
4 changes: 2 additions & 2 deletions funsol-billing-utils/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ afterEvaluate {
from components.release
groupId = 'com.github.Funsol-Projects'
artifactId = 'Funsol-Billing-Helper'
version = 'v2.0.1'
version = 'v2.0.2'
}
debug(MavenPublication) {
from components.debug
groupId = 'com.github.Funsol-Projects'
artifactId = 'Funsol-Billing-Helper'
version = 'v2.0.1'
version = 'v2.0.2'
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,10 @@ class FunSolBillingHelper(private val context: Context) {
client.queryProductDetailsAsync(queryProductDetailsParams) { billingResult, productDetailsList ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
productDetailsList.forEach { productDetails ->
logFunsolBilling("Subscription product details: $productDetails")
allProducts.add(productDetails)
if (!allProducts.contains(productDetails)) {
logFunsolBilling("Subscription product details: $productDetails")
allProducts.add(productDetails)
}
}
} else {
logFunsolBilling("Failed to retrieve SUBS prices: ${billingResult.debugMessage}")
Expand Down Expand Up @@ -163,8 +165,10 @@ class FunSolBillingHelper(private val context: Context) {
client.queryProductDetailsAsync(queryProductDetailsParams) { billingResult, productDetailsList ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
productDetailsList.forEach { productDetails ->
logFunsolBilling("In-app product details: $productDetails")
allProducts.add(productDetails)
if (!allProducts.contains(productDetails)) {
logFunsolBilling("In-app product details: $productDetails")
allProducts.add(productDetails)
}
}
} else {
logFunsolBilling("Failed to retrieve In-APP prices: ${billingResult.debugMessage}")
Expand Down Expand Up @@ -223,9 +227,6 @@ class FunSolBillingHelper(private val context: Context) {
BillingClient.BillingResponseCode.OK -> {
purchases?.let {
for (purchase in it) {
logFunsolBilling("purchased --> $purchase")
logFunsolBilling("purchased First --> ${purchase.products.first()}")
// val productPriceInfo = getAllProductPrices().find { it.productId == purchase }
CoroutineScope(IO).launch {
lastPurchasedProduct?.let { originalProduct ->
val updatedProduct = originalProduct.copy(orderId = purchase.orderId)
Expand Down Expand Up @@ -485,10 +486,15 @@ class FunSolBillingHelper(private val context: Context) {
}

fun release() {
if (billingClient != null && billingClient!!.isReady) {
billingClient?.takeIf { it.isReady }?.apply {
logFunsolBilling("BillingHelper instance release: ending connection...")
billingClient?.endConnection()
endConnection()
}
consumeAbleProductIds.clear()
purchasedSubsProductList.clear()
purchasedInAppProductList.clear()
allProducts.clear()
billingClient = null
}

fun setBillingEventListener(billingEventListeners: BillingEventListener?): FunSolBillingHelper {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ class BuyProducts {
type = productInfo.productType,
price = productPriceInfo.price,
priceMicro = productPriceInfo.priceMicro,
currencyCode = productPriceInfo.currencyCode)
currencyCode = productPriceInfo.currencyCode,
currencySymbol = productPriceInfo.currencySymbol)
}

} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,98 +7,83 @@ import com.funsol.iap.billing.helper.BillingData.billingEventListener
import com.funsol.iap.billing.model.ErrorType

class ProductDetail {

/**
* Retrieves the details of a specific product (in-app or subscription) based on the provided keys.
*
* @param productId The key of the product (product ID for in-app or base plan ID for subscriptions).
* @param offerId The offer key for subscriptions, optional for in-app products. Defaults to null.
* @param productType The type of the product, either "INAPP" or "SUBS".
* @return The [ProductDetails] of the product if found, otherwise null.
*/
fun getProductDetail(productId: String, offerId: String? = null, productType: String): ProductDetails? {
// Return error if no products are available
if (allProducts.isEmpty()) {
billingEventListener?.onBillingError(ErrorType.PRODUCT_NOT_EXIST)
return null
}

// Locate product based on type and keys
val product = allProducts.find { product ->
when (productType) {
BillingClient.ProductType.INAPP -> {
if (product.productId == productId) {
logFunsolBilling("In-App product detail: title=${product.title}, price=${product.oneTimePurchaseOfferDetails?.formattedPrice}")
true
} else {
false
}
}

BillingClient.ProductType.SUBS -> product.subscriptionOfferDetails?.any { subDetails ->
val isMatchingBasePlan = subDetails.basePlanId.equals(productId, true)
val isMatchingOfferId = (offerId == null || subDetails.offerId == offerId)

if (isMatchingBasePlan && isMatchingOfferId) {
logFunsolBilling("Subscription product detail: basePlanId = ${subDetails.basePlanId}, offerId = ${subDetails.offerId}")
}

isMatchingBasePlan && isMatchingOfferId
} == true

else -> false
}
}

// Handle missing product
if (product == null) {
billingEventListener?.onBillingError(ErrorType.PRODUCT_NOT_EXIST)
}

return product
}


/**
* Retrieves the offer token for a given product ID and optional offer ID from the provided list of subscription offers.
*
* @param offerList The list of subscription offer details.
* @param basePlanId The base plan ID to match with the base plan ID in the offer details.
* @param offerId The offer ID to match with the offer details (optional, default is null).
* @return The offer token if a matching offer is found, otherwise an empty string.
*/
fun getOfferToken(offerList: List<ProductDetails.SubscriptionOfferDetails>?, basePlanId: String, offerId: String? = null): String {
offerList?.forEach { product ->
// Check for a matching offer with basePlanId and offerId (if provided)
if (product.basePlanId == basePlanId && (offerId == null || product.offerId == offerId)) {
return product.offerToken
}

// Check for the case when offerId is null and product has no offerId
if (offerId == null && product.basePlanId == basePlanId && product.offerId == null) {
return product.offerToken
}
}

// Log and return an empty string if no matching offer is found
logFunsolBilling("No offer found for basePlanId: $basePlanId and offerId: ${offerId ?: "null"}")
return ""
}

fun getProductType(productKey: String): String {
allProducts.forEach { productDetail ->
if (productDetail.productType == BillingClient.ProductType.INAPP) {
if (productDetail.productId == productKey) {
return productDetail.productType
}
} else {
productDetail.subscriptionOfferDetails?.forEach {
if (it.basePlanId == productKey) {
return productDetail.productType
}
}
}
}
return ""
}

/**
* Retrieves the details of a specific product (in-app or subscription) based on the provided keys.
*
* @param productId The key of the product (product ID for in-app or base plan ID for subscriptions).
* @param offerId The offer key for subscriptions, optional for in-app products. Defaults to null.
* @param productType The type of the product, either "INAPP" or "SUBS".
* @return The [ProductDetails] of the product if found, otherwise null.
*/
fun getProductDetail(productId: String, offerId: String? = null, productType: String): ProductDetails? {
// Return error if no products are available
if (allProducts.isEmpty()) {
billingEventListener?.onBillingError(ErrorType.PRODUCT_NOT_EXIST)
return null
}

// Locate product based on type and keys
val product = allProducts.find { productDetails ->
if (productDetails.productType == BillingClient.ProductType.SUBS) {
// Check if any subscription offer matches
productDetails.subscriptionOfferDetails?.any { subDetails ->
subDetails.basePlanId == productId && subDetails.offerId == offerId
} == true
} else if (productDetails.productType == BillingClient.ProductType.INAPP) {
// Check if it's an in-app product
if (productDetails.productId == productId) {
true
} else {
false
}
} else {
false
}
}

// Handle missing product
if (product == null) {
billingEventListener?.onBillingError(ErrorType.PRODUCT_NOT_EXIST)
}

return product
}

/**
* Retrieves the offer token for a given product ID and optional offer ID from the provided list of subscription offers.
*
* @param offerList The list of subscription offer details.
* @param basePlanId The base plan ID to match with the base plan ID in the offer details.
* @param offerId The offer ID to match with the offer details (optional, default is null).
* @return The offer token if a matching offer is found, otherwise an empty string.
*/
fun getOfferToken(offerList: List<ProductDetails.SubscriptionOfferDetails>?, basePlanId: String, offerId: String? = null): String {
offerList?.forEach { product ->
// Check for a matching offer with basePlanId and offerId (if provided)
if (product.basePlanId == basePlanId && product.offerId == offerId) {
return product.offerToken
}
}
// Log and return an empty string if no matching offer is found
logFunsolBilling("No offer found for basePlanId: $basePlanId and offerId: ${offerId ?: "null"}")
return ""
}

fun getProductType(productKey: String): String {
allProducts.forEach { productDetail ->
if (productDetail.productType == BillingClient.ProductType.INAPP) {
if (productDetail.productId == productKey) {
return productDetail.productType
}
} else {
productDetail.subscriptionOfferDetails?.forEach {
if (it.basePlanId == productKey) {
return productDetail.productType
}
}
}
}
return ""
}
}
Loading

0 comments on commit b7a7d91

Please sign in to comment.