@@ -4,9 +4,8 @@ import com.android.billingclient.api.BillingClient
44import com.android.billingclient.api.Purchase
55import com.android.billingclient.api.SkuDetails
66import io.reactivex.Completable
7- import io.reactivex.Maybe
87import io.reactivex.Single
9- import io.reactivex.rxkotlin.Maybes.zip
8+ import io.reactivex.rxkotlin.Singles
109import io.reactivex.subjects.PublishSubject
1110import org.stepik.android.domain.base.DataSourceType
1211import org.stepik.android.domain.billing.exception.NoPurchasesToRestoreException
@@ -18,7 +17,10 @@ import org.stepik.android.domain.course_payments.exception.CoursePurchaseVerific
1817import org.stepik.android.domain.course_payments.model.CoursePayment
1918import org.stepik.android.domain.course_payments.model.PromoCodeSku
2019import org.stepik.android.domain.course_payments.repository.CoursePaymentsRepository
20+ import org.stepik.android.domain.course_purchase.model.BillingPurchasePayload
2121import org.stepik.android.domain.course_purchase.model.CoursePurchaseObfuscatedParams
22+ import org.stepik.android.domain.course_purchase.model.PurchaseFlowData
23+ import org.stepik.android.domain.course_purchase.repository.BillingPurchasePayloadRepository
2224import org.stepik.android.domain.lesson.repository.LessonRepository
2325import org.stepik.android.domain.mobile_tiers.repository.LightSkuRepository
2426import org.stepik.android.domain.mobile_tiers.repository.MobileTiersRepository
@@ -27,7 +29,7 @@ import org.stepik.android.domain.user_courses.interactor.UserCoursesInteractor
2729import org.stepik.android.model.Course
2830import org.stepik.android.remote.mobile_tiers.model.MobileTierCalculation
2931import org.stepik.android.view.injection.course.EnrollmentCourseUpdates
30- import ru.nobird.android.domain.rx.maybeFirst
32+ import ru.nobird.android.domain.rx.toMaybe
3133import javax.inject.Inject
3234
3335class CoursePurchaseInteractor
@@ -42,6 +44,7 @@ constructor(
4244 private val courseRepository: CourseRepository ,
4345 private val lessonRepository: LessonRepository ,
4446 private val profileRepository: ProfileRepository ,
47+ private val billingPurchasePayloadRepository: BillingPurchasePayloadRepository ,
4548
4649 @EnrollmentCourseUpdates
4750 private val enrollmentSubject: PublishSubject <Course >
@@ -60,7 +63,7 @@ constructor(
6063 }
6164 }
6265
63- fun fetchPurchaseFlowData (courseId : Long , skuId : String ): Single <Pair < CoursePurchaseObfuscatedParams , SkuDetails > > =
66+ fun fetchPurchaseFlowData (courseId : Long , skuId : String ): Single <PurchaseFlowData > =
6467 coursePaymentsRepository
6568 .getCoursePaymentsByCourseId(courseId, CoursePayment .Status .SUCCESS , sourceType = DataSourceType .REMOTE )
6669 .flatMap { payments ->
@@ -71,7 +74,48 @@ constructor(
7174 }
7275 }
7376
74- fun completePurchase (courseId : Long , sku : SkuDetails , purchase : Purchase , promoCodeName : String? = null): Completable =
77+ fun consumePurchase (
78+ courseId : Long ,
79+ profileId : Long ,
80+ sku : SkuDetails ,
81+ purchase : Purchase ,
82+ promoCodeName : String? = null
83+ ): Completable =
84+ saveBillingPurchasePayload(courseId, profileId, purchase, promoCodeName)
85+ .andThen(completePurchase(courseId, sku, purchase, promoCodeName))
86+
87+ fun restorePurchase (courseId : Long ): Completable =
88+ Singles .zip(
89+ getCurrentProfileId(),
90+ billingRepository.getAllPurchases(BillingClient .SkuType .INAPP )
91+ ).flatMapMaybe { (profileId, purchases) ->
92+ val obfuscatedParams = createCoursePurchaseObfuscatedParams(profileId, courseId)
93+ val purchase =
94+ purchases.find {
95+ it.accountIdentifiers?.obfuscatedAccountId == obfuscatedParams.obfuscatedAccountId &&
96+ it.accountIdentifiers?.obfuscatedProfileId == obfuscatedParams.obfuscatedProfileId
97+ }
98+ purchase.toMaybe()
99+ }
100+ .switchIfEmpty(Single .error(NoPurchasesToRestoreException ()))
101+ .flatMapCompletable { purchase ->
102+ completePurchaseRestore(courseId, purchase)
103+ }
104+
105+ private fun saveBillingPurchasePayload (courseId : Long , profileId : Long , purchase : Purchase , promoCodeName : String? ): Completable =
106+ billingPurchasePayloadRepository
107+ .saveBillingPurchasePayload(
108+ BillingPurchasePayload (
109+ orderId = purchase.orderId,
110+ courseId = courseId,
111+ profileId = profileId,
112+ obfuscatedAccountId = purchase.accountIdentifiers?.obfuscatedAccountId.orEmpty(),
113+ obfuscatedProfileId = purchase.accountIdentifiers?.obfuscatedProfileId.orEmpty(),
114+ promoCode = promoCodeName
115+ )
116+ )
117+
118+ private fun completePurchase (courseId : Long , sku : SkuDetails , purchase : Purchase , promoCodeName : String? = null): Completable =
75119 coursePaymentsRepository
76120 .createCoursePayment(courseId, sku, purchase, promoCodeName)
77121 .flatMapCompletable { payment ->
@@ -82,35 +126,38 @@ constructor(
82126 }
83127 }
84128 .andThen(billingRepository.consumePurchase(purchase))
129+ .andThen(billingPurchasePayloadRepository.deleteBillingPurchasePayload(purchase.orderId))
85130 .andThen(updateCourseAfterEnrollment(courseId))
86131
87- fun fetchSkuDetailsAndPurchase (skuId : String ): Maybe <Pair <SkuDetails , Purchase >> =
88- zip(
89- billingRepository
90- .getInventory(BillingClient .SkuType .INAPP , skuId),
91- billingRepository
92- .getAllPurchases(BillingClient .SkuType .INAPP , listOf (skuId))
93- .maybeFirst()
94- )
95-
96- fun restorePurchase (courseId : Long , sku : SkuDetails , purchase : Purchase ): Completable =
97- getCurrentProfileId()
98- .filter { profileId ->
99- val obfuscatedParams = createCoursePurchaseObfuscatedParams(profileId, courseId)
100- purchase.accountIdentifiers?.obfuscatedAccountId == obfuscatedParams.obfuscatedAccountId && purchase.accountIdentifiers?.obfuscatedProfileId == obfuscatedParams.obfuscatedProfileId
101- }
102- .switchIfEmpty(Single .error(NoPurchasesToRestoreException ()))
103- .flatMapCompletable {
104- completePurchase(courseId, sku, purchase)
105- }
132+ private fun completePurchaseRestore (courseId : Long , purchase : Purchase ): Completable =
133+ Singles .zip(
134+ billingPurchasePayloadRepository.getBillingPurchasePayload(purchase.orderId).onErrorReturnItem(BillingPurchasePayload .EMPTY ),
135+ billingRepository.getInventory(BillingClient .SkuType .INAPP , purchase.skus.first()).toSingle()
136+ ).flatMapCompletable { (billingPurchasePayload, skuDetails) ->
137+ val promoCode =
138+ if (billingPurchasePayload.obfuscatedAccountId == purchase.accountIdentifiers?.obfuscatedAccountId &&
139+ billingPurchasePayload.obfuscatedProfileId == purchase.accountIdentifiers?.obfuscatedProfileId
140+ ) {
141+ billingPurchasePayload.promoCode
142+ } else {
143+ null
144+ }
145+ completePurchase(courseId, skuDetails, purchase, promoCode)
146+ }
106147
107- private fun getSkuDetails (courseId : Long , skuId : String ): Single <Pair < CoursePurchaseObfuscatedParams , SkuDetails > > =
148+ private fun getSkuDetails (courseId : Long , skuId : String ): Single <PurchaseFlowData > =
108149 getCurrentProfileId()
109150 .flatMap { profileId ->
110151 billingRepository
111152 .getInventory(BillingClient .SkuType .INAPP , skuId)
112153 .toSingle()
113- .map { skuDetails -> createCoursePurchaseObfuscatedParams(profileId, courseId) to skuDetails }
154+ .map { skuDetails ->
155+ PurchaseFlowData (
156+ CoursePurchasePayload (profileId, courseId),
157+ createCoursePurchaseObfuscatedParams(profileId, courseId),
158+ skuDetails
159+ )
160+ }
114161 }
115162
116163 private fun updateCourseAfterEnrollment (courseId : Long ): Completable =
0 commit comments