Skip to content

Commit ff8fd93

Browse files
Merge pull request #48 from shayanpourvatan/feature-developer-discount
Add dynamicPriceToken capability
2 parents af1fdda + 9824367 commit ff8fd93

10 files changed

Lines changed: 215 additions & 62 deletions

File tree

app/src/main/java/ir/cafebazaar/poolakeysample/MainActivity.kt

Lines changed: 74 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import ir.cafebazaar.poolakey.Payment
1111
import ir.cafebazaar.poolakey.callback.PurchaseQueryCallback
1212
import ir.cafebazaar.poolakey.config.PaymentConfiguration
1313
import ir.cafebazaar.poolakey.config.SecurityCheck
14+
import ir.cafebazaar.poolakey.exception.DynamicPriceNotSupportedException
1415
import ir.cafebazaar.poolakey.request.PurchaseRequest
1516
import kotlinx.android.synthetic.main.activity_main.consumeSwitch
17+
import kotlinx.android.synthetic.main.activity_main.dynamicPriceToken
1618
import kotlinx.android.synthetic.main.activity_main.getSkuDetailInAppButton
1719
import kotlinx.android.synthetic.main.activity_main.getSkuDetailSubscriptionButton
1820
import kotlinx.android.synthetic.main.activity_main.purchaseButton
@@ -44,40 +46,22 @@ class MainActivity : AppCompatActivity() {
4446
private fun setViewClickListener() {
4547
purchaseButton.setOnClickListener {
4648
if (paymentConnection.getState() == ConnectionState.Connected) {
47-
payment.purchaseProduct(
48-
activity = this,
49-
request = PurchaseRequest(
50-
productId = skuValueInput.text.toString(),
51-
requestCode = PURCHASE_REQUEST_CODE,
52-
payload = "payload"
53-
)
54-
) {
55-
purchaseFlowBegan {
56-
toast(R.string.general_purchase_flow_began_message)
57-
}
58-
failedToBeginFlow {
59-
toast(R.string.general_purchase_failed_message)
60-
}
61-
}
49+
purchaseProduct(
50+
productId = skuValueInput.text.toString(),
51+
requestCode = PURCHASE_REQUEST_CODE,
52+
payload = "payload",
53+
dynamicPriceToken = dynamicPriceToken.text.toString()
54+
)
6255
}
6356
}
6457
subscribeButton.setOnClickListener {
6558
if (paymentConnection.getState() == ConnectionState.Connected) {
66-
payment.subscribeProduct(
67-
activity = this@MainActivity,
68-
request = PurchaseRequest(
69-
productId = skuValueInput.text.toString(),
70-
requestCode = SUBSCRIBE_REQUEST_CODE,
71-
payload = ""
72-
)
73-
) {
74-
purchaseFlowBegan {
75-
toast(R.string.general_purchase_flow_began_message)
76-
}
77-
failedToBeginFlow {
78-
toast(R.string.general_purchase_failed_message)
79-
}
80-
}
59+
subscribeProduct(
60+
productId = skuValueInput.text.toString(),
61+
requestCode = SUBSCRIBE_REQUEST_CODE,
62+
payload = "",
63+
dynamicPriceToken = dynamicPriceToken.text.toString()
64+
)
8165
}
8266
}
8367
queryPurchasedItemsButton.setOnClickListener {
@@ -93,6 +77,66 @@ class MainActivity : AppCompatActivity() {
9377
setGetSkuDetailClickListener()
9478
}
9579

80+
private fun subscribeProduct(
81+
productId: String,
82+
requestCode: Int,
83+
payload: String,
84+
dynamicPriceToken: String?
85+
) {
86+
payment.subscribeProduct(
87+
activity = this@MainActivity,
88+
request = PurchaseRequest(
89+
productId = skuValueInput.text.toString(),
90+
requestCode = SUBSCRIBE_REQUEST_CODE,
91+
payload = "",
92+
dynamicPriceToken = dynamicPriceToken
93+
)
94+
) {
95+
purchaseFlowBegan {
96+
toast(R.string.general_purchase_flow_began_message)
97+
}
98+
failedToBeginFlow {
99+
// bazaar need to update, in this case we only launch purchase without discount
100+
if (it is DynamicPriceNotSupportedException) {
101+
toast(R.string.general_purchase_failed_dynamic_price_token_message)
102+
subscribeProduct(productId, requestCode, payload, null)
103+
} else {
104+
toast(R.string.general_purchase_failed_message)
105+
}
106+
}
107+
}
108+
}
109+
110+
private fun purchaseProduct(
111+
productId: String,
112+
requestCode: Int,
113+
payload: String,
114+
dynamicPriceToken: String?
115+
) {
116+
payment.purchaseProduct(
117+
activity = this,
118+
request = PurchaseRequest(
119+
productId = productId,
120+
requestCode = requestCode,
121+
payload = payload,
122+
dynamicPriceToken = dynamicPriceToken
123+
)
124+
) {
125+
purchaseFlowBegan {
126+
toast(R.string.general_purchase_flow_began_message)
127+
}
128+
failedToBeginFlow {
129+
// bazaar need to update, in this case we only launch purchase without discount
130+
if (it is DynamicPriceNotSupportedException) {
131+
toast(R.string.general_purchase_failed_dynamic_price_token_message)
132+
purchaseProduct(productId, requestCode, payload, null)
133+
} else {
134+
toast(R.string.general_purchase_failed_message)
135+
}
136+
}
137+
}
138+
}
139+
96140
private fun setGetSkuDetailClickListener() {
97141

98142
getSkuDetailInAppButton.setOnClickListener {

app/src/main/res/layout/activity_main.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@
2727
android:hint="@string/general_product_id_hint"
2828
android:padding="16dp" />
2929

30+
<androidx.appcompat.widget.AppCompatEditText
31+
android:id="@+id/dynamicPriceToken"
32+
android:layout_width="match_parent"
33+
android:layout_height="56dp"
34+
android:hint="@string/general_dynamic_price_token_hint"
35+
android:padding="16dp" />
36+
3037
<androidx.appcompat.widget.SwitchCompat
3138
android:id="@+id/consumeSwitch"
3239
android:layout_width="match_parent"

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<string name="general_subscribe_text">Subscribe</string>
77
<string name="general_consume_purchase_text">Consume Purchase</string>
88
<string name="general_product_id_hint">Product id</string>
9+
<string name="general_dynamic_price_token_hint">Dynamic price token</string>
910
<string name="general_service_connection_not_connected_text">Service: Not Connected</string>
1011
<string name="general_service_connection_connected_text">Service: Connected</string>
1112
<string name="general_service_connection_failed_text">Service: Failed to Connect</string>
@@ -18,6 +19,7 @@
1819
<string name="general_purchase_succeed_message">Purchase succeed</string>
1920
<string name="general_purchase_cancelled_message">Purchase Cancelled</string>
2021
<string name="general_purchase_failed_message">Purchase failed</string>
22+
<string name="general_purchase_failed_dynamic_price_token_message">Dynamic price token not supported in this bazaar version</string>
2123
<string name="general_consume_succeed_message">Consume succeed</string>
2224
<string name="general_consume_failed_message">Consume failed</string>
2325
<string name="general_query_purchased_items_failed_message">Query failed</string>

poolakey/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,10 @@ interface IInAppBillingService {
4242

4343
Bundle getPurchaseConfig(int apiVersion);
4444

45+
Bundle getBuyIntentV3(
46+
int apiVersion,
47+
String packageName,
48+
String sku,
49+
String developerPayload,
50+
in Bundle extraData);
4551
}

poolakey/src/main/java/ir/cafebazaar/poolakey/billing/connection/ReceiverBillingConnection.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import ir.cafebazaar.poolakey.getPackageInfo
3333
import ir.cafebazaar.poolakey.receiver.BillingReceiver
3434
import ir.cafebazaar.poolakey.receiver.BillingReceiverCommunicator
3535
import ir.cafebazaar.poolakey.request.PurchaseRequest
36+
import ir.cafebazaar.poolakey.request.purchaseExtraData
3637
import ir.cafebazaar.poolakey.sdkAwareVersionCode
3738
import ir.cafebazaar.poolakey.security.Security
3839
import ir.cafebazaar.poolakey.takeIf
@@ -212,10 +213,10 @@ internal class ReceiverBillingConnection(
212213
putExtra(KEY_SKU, purchaseRequest.productId)
213214
putExtra(KEY_DEVELOPER_PAYLOAD, purchaseRequest.payload)
214215
putExtra(KEY_ITEM_TYPE, purchaseType.type)
216+
putExtra(KEY_EXTRA_INFO, purchaseRequest.purchaseExtraData())
215217
}.run(::sendBroadcast)
216218
}
217219

218-
219220
override fun stopConnection() {
220221
disconnected = true
221222

@@ -437,6 +438,7 @@ internal class ReceiverBillingConnection(
437438
private const val KEY_SKU = "sku"
438439
private const val KEY_ITEM_TYPE = "itemType"
439440
private const val KEY_DEVELOPER_PAYLOAD = "developerPayload"
441+
private const val KEY_EXTRA_INFO = "extraInfo"
440442
private const val KEY_SECURE = "secure"
441443
private const val KEY_RESPONSE_BUY_INTENT = "BUY_INTENT"
442444
private const val RESPONSE_CODE = "RESPONSE_CODE"

poolakey/src/main/java/ir/cafebazaar/poolakey/billing/purchase/PurchaseFunction.kt

Lines changed: 101 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import ir.cafebazaar.poolakey.billing.BillingFunction
1111
import ir.cafebazaar.poolakey.callback.PurchaseIntentCallback
1212
import ir.cafebazaar.poolakey.constant.BazaarIntent
1313
import ir.cafebazaar.poolakey.constant.Billing
14+
import ir.cafebazaar.poolakey.exception.DynamicPriceNotSupportedException
1415
import ir.cafebazaar.poolakey.exception.ResultNotOkayException
1516
import ir.cafebazaar.poolakey.request.PurchaseRequest
17+
import ir.cafebazaar.poolakey.request.purchaseExtraData
1618
import ir.cafebazaar.poolakey.takeIf
1719

1820
internal class PurchaseFunction(
@@ -34,38 +36,91 @@ internal class PurchaseFunction(
3436
.invoke(IllegalStateException("Couldn't receive buy intent from Bazaar"))
3537
}
3638

37-
if (doesClientSupportIntentV2(purchaseConfigBundle)) {
38-
getBuyIntentV2FromBillingService(
39-
billingService,
40-
purchaseRequest,
41-
purchaseType,
42-
callback
43-
)?.takeIf(
44-
thisIsTrue = { bundle ->
45-
bundle.getParcelable<PendingIntent>(INTENT_RESPONSE_BUY) != null
46-
}, andIfNot = intentResponseIsNullError
47-
)?.getParcelable<Intent>(INTENT_RESPONSE_BUY)?.also { purchaseIntent ->
48-
fireIntentWithIntent.invoke(purchaseIntent)
39+
when {
40+
isBazaarSupportIntentV3(purchaseConfigBundle) -> {
41+
launchBuyIntentV3(billingService, intentResponseIsNullError)
4942
}
50-
} else {
51-
getBuyIntentFromBillingService(
52-
billingService,
53-
purchaseRequest,
54-
purchaseType,
55-
callback
56-
)?.takeIf(
57-
thisIsTrue = { bundle ->
58-
bundle.getParcelable<PendingIntent>(INTENT_RESPONSE_BUY) != null
59-
}, andIfNot = intentResponseIsNullError
60-
)?.getParcelable<PendingIntent>(INTENT_RESPONSE_BUY)?.also { purchaseIntent ->
61-
fireIntentWithIntentSender.invoke(purchaseIntent.intentSender)
43+
isBazaarSupportIntentV2(purchaseConfigBundle) -> {
44+
launchBuyIntentV2(billingService, intentResponseIsNullError)
45+
}
46+
else -> {
47+
launchBuyIntent(billingService, intentResponseIsNullError)
6248
}
6349
}
6450
} catch (e: RemoteException) {
6551
PurchaseIntentCallback().apply(callback).failedToBeginFlow.invoke(e)
6652
}
6753
}
6854

55+
private fun PurchaseFunctionRequest.launchBuyIntentV3(
56+
billingService: IInAppBillingService,
57+
intentResponseIsNullError: () -> Unit
58+
) {
59+
getBuyIntentV3FromBillingService(
60+
billingService,
61+
purchaseRequest,
62+
purchaseRequest.purchaseExtraData(),
63+
callback
64+
)?.takeIf(
65+
thisIsTrue = { bundle ->
66+
bundle.getParcelable<PendingIntent>(INTENT_RESPONSE_BUY) != null
67+
}, andIfNot = intentResponseIsNullError
68+
)?.getParcelable<Intent>(INTENT_RESPONSE_BUY)?.also { purchaseIntent ->
69+
launchIntent.invoke(purchaseIntent)
70+
}
71+
}
72+
73+
private fun PurchaseFunctionRequest.launchBuyIntentV2(
74+
billingService: IInAppBillingService,
75+
intentResponseIsNullError: () -> Unit
76+
) {
77+
78+
if (purchaseRequest.dynamicPriceToken.isNullOrEmpty().not()) {
79+
PurchaseIntentCallback().apply(callback).failedToBeginFlow.invoke(
80+
DynamicPriceNotSupportedException()
81+
)
82+
return
83+
}
84+
85+
getBuyIntentV2FromBillingService(
86+
billingService,
87+
purchaseRequest,
88+
purchaseType,
89+
callback
90+
)?.takeIf(
91+
thisIsTrue = { bundle ->
92+
bundle.getParcelable<PendingIntent>(INTENT_RESPONSE_BUY) != null
93+
}, andIfNot = intentResponseIsNullError
94+
)?.getParcelable<Intent>(INTENT_RESPONSE_BUY)?.also { purchaseIntent ->
95+
launchIntent.invoke(purchaseIntent)
96+
}
97+
}
98+
99+
private fun PurchaseFunctionRequest.launchBuyIntent(
100+
billingService: IInAppBillingService,
101+
intentResponseIsNullError: () -> Unit
102+
) {
103+
if (purchaseRequest.dynamicPriceToken.isNullOrEmpty().not()) {
104+
PurchaseIntentCallback().apply(callback).failedToBeginFlow.invoke(
105+
DynamicPriceNotSupportedException()
106+
)
107+
return
108+
}
109+
110+
getBuyIntentFromBillingService(
111+
billingService,
112+
purchaseRequest,
113+
purchaseType,
114+
callback
115+
)?.takeIf(
116+
thisIsTrue = { bundle ->
117+
bundle.getParcelable<PendingIntent>(INTENT_RESPONSE_BUY) != null
118+
}, andIfNot = intentResponseIsNullError
119+
)?.getParcelable<PendingIntent>(INTENT_RESPONSE_BUY)?.also { purchaseIntent ->
120+
launchIntentWithIntentSender.invoke(purchaseIntent.intentSender)
121+
}
122+
}
123+
69124
private inline fun getBuyIntentFromBillingService(
70125
billingService: IInAppBillingService,
71126
purchaseRequest: PurchaseRequest,
@@ -92,25 +147,43 @@ internal class PurchaseFunction(
92147
purchaseRequest.payload
93148
)?.takeBundleIfResponseIsOk(callback)
94149

150+
private inline fun getBuyIntentV3FromBillingService(
151+
billingService: IInAppBillingService,
152+
purchaseRequest: PurchaseRequest,
153+
extraData: Bundle,
154+
callback: PurchaseIntentCallback.() -> Unit
155+
) = billingService.getBuyIntentV3(
156+
Billing.IN_APP_BILLING_VERSION,
157+
context.packageName,
158+
purchaseRequest.productId,
159+
purchaseRequest.payload,
160+
extraData
161+
)?.takeBundleIfResponseIsOk(callback)
162+
95163
private inline fun Bundle.takeBundleIfResponseIsOk(
96164
callback: PurchaseIntentCallback.() -> Unit
97165
): Bundle? = takeIf(
98166
thisIsTrue = { bundle ->
99167
bundle.get(BazaarIntent.RESPONSE_CODE) == BazaarIntent.RESPONSE_RESULT_OK
100168
}, andIfNot = {
101-
PurchaseIntentCallback().apply(callback)
102-
.failedToBeginFlow
103-
.invoke(ResultNotOkayException())
169+
PurchaseIntentCallback().apply(callback)
170+
.failedToBeginFlow
171+
.invoke(ResultNotOkayException())
104172
}
105173
)
106174

107-
private fun doesClientSupportIntentV2(purchaseConfigBundle: Bundle?): Boolean {
175+
private fun isBazaarSupportIntentV2(purchaseConfigBundle: Bundle?): Boolean {
108176
return purchaseConfigBundle?.getBoolean(INTENT_V2_SUPPORT) ?: false
109177
}
110178

179+
private fun isBazaarSupportIntentV3(purchaseConfigBundle: Bundle?): Boolean {
180+
return purchaseConfigBundle?.getBoolean(INTENT_V3_SUPPORT) ?: false
181+
}
182+
111183
companion object {
112184
private const val INTENT_RESPONSE_BUY = "BUY_INTENT"
113185
private const val INTENT_V2_SUPPORT = "INTENT_V2_SUPPORT"
186+
private const val INTENT_V3_SUPPORT = "INTENT_V3_SUPPORT"
114187
}
115188

116189
}

poolakey/src/main/java/ir/cafebazaar/poolakey/billing/purchase/PurchaseFunctionRequest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ internal class PurchaseFunctionRequest(
1111
val purchaseRequest: PurchaseRequest,
1212
val purchaseType: PurchaseType,
1313
val callback: PurchaseIntentCallback.() -> Unit,
14-
val fireIntentWithIntentSender: (IntentSender) -> Unit,
15-
val fireIntentWithIntent: (Intent) -> Unit
14+
val launchIntentWithIntentSender: (IntentSender) -> Unit,
15+
val launchIntent: (Intent) -> Unit
1616
) : FunctionRequest

poolakey/src/main/java/ir/cafebazaar/poolakey/constant/BazaarIntent.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ internal object BazaarIntent {
1212
const val RESPONSE_DATA_SIGNATURE_LIST = "INAPP_DATA_SIGNATURE_LIST"
1313
const val RESPONSE_GET_SKU_DETAILS_LIST = "DETAILS_LIST"
1414
const val REQUEST_SKU_DETAILS_LIST = "ITEM_ID_LIST"
15+
const val RESPONSE_DYNAMIC_PRICE_TOKEN = "DYNAMIC_PRICE_TOKEN"
1516
}

0 commit comments

Comments
 (0)