Skip to content

Commit

Permalink
fix display for monthly subs
Browse files Browse the repository at this point in the history
  • Loading branch information
phillipthelen committed Nov 4, 2024
1 parent 2688081 commit bc20317
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 94 deletions.
4 changes: 4 additions & 0 deletions HabitRPG/UI/Inventory/ShopViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ class ShopViewController: BaseCollectionViewController, ShopCollectionViewDataSo
refresher = HabiticaRefresControl()
refresher.addTarget(self, action: #selector(refresh), for: .valueChanged)
collectionView.addSubview(refresher)

userRepository.getUser().on(value: {[weak self] user in
self?.updateNavBar(gold: Int(user.stats?.gold ?? 0.0), gems: user.gemCount, hourglasses: user.purchased?.subscriptionPlan?.consecutive?.hourglasses ?? 0)
}).start()
}

private var isSubscribed: Bool?
Expand Down
2 changes: 1 addition & 1 deletion HabitRPG/UI/Purchases/SubscriptionBenefitView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ struct SubscriptionBenefitView<Icon: View, Title: View, Description: View>: View
.cornerRadius(8)
VStack(alignment: .leading, spacing: 4) {
title.font(.system(size: 15, weight: .semibold))
description.font(.system(size: 13)).lineSpacing(3)
description.font(.system(size: 13)).lineSpacing(2)
}.frame(maxWidth: .infinity, alignment: .leading)
}.padding(.leading, 12)
.padding(.vertical, 8)
Expand Down
38 changes: 20 additions & 18 deletions HabitRPG/UI/Purchases/SubscriptionDetailViewUI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ struct SubscriptionDetailViewUI: View {
var paymentTypeText: String {
if plan.isGifted {
return L10n.Subscription.gifted
} else if plan.dateTerminated != nil {
} else if plan.dateTerminated != nil || PurchaseHandler.shared.wasSubscriptionCancelled == true {
return L10n.cancelled
} else if plan.paymentMethod == "Apple" {
if let nextEstimatedPayment = plan.nextEstimatedPayment {
Expand Down Expand Up @@ -272,24 +272,26 @@ struct SubscriptionDetailViewUI: View {
Image(Asset.hourglassBannerRight.name)
}
}
DetailContainer(verticalPadding: 16) {
VStack(alignment: .leading, spacing: 6) {
Group {
if plan.isGifted {
Text(L10n.continueBenefits)
} else if plan.dateTerminated != nil {
Text(L10n.resubscribe)
} else if plan.paymentMethod == "Apple" {
Text(L10n.editCancelSubscription)
} else {
Text(L10n.cancelSubscription)
if !(plan.isGifted && plan.isActive) {
DetailContainer(verticalPadding: 16) {
VStack(alignment: .leading, spacing: 6) {
Group {
if plan.isGifted {
Text(L10n.continueBenefits)
} else if plan.dateTerminated != nil {
Text(L10n.resubscribe)
} else if plan.paymentMethod == "Apple" {
Text(L10n.editCancelSubscription)
} else {
Text(L10n.cancelSubscription)
}
}.font(.system(size: 15, weight: .semibold))
Text(cancelDescription).font(.system(size: 13))
if let text = cancelButtonText {
HabiticaButtonUI(label: Text(text).foregroundColor(.purple100), color: .yellow100, size: .compact) {
cancelSubscription()
}.padding(.top, 7)
}
}.font(.system(size: 15, weight: .semibold))
Text(cancelDescription).font(.system(size: 13))
if let text = cancelButtonText {
HabiticaButtonUI(label: Text(text).foregroundColor(.purple100), color: .yellow100, size: .compact) {
cancelSubscription()
}.padding(.top, 7)
}
}
}
Expand Down
84 changes: 20 additions & 64 deletions HabitRPG/UI/Purchases/SubscriptionPage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,26 +111,30 @@ class SubscriptionViewModel: BaseSubscriptionViewModel {
super.init()

userRepository.getUser().on(value: {[weak self] user in
if (self?.scrollToTop == nil && self?.isSubscribed == false && user.isSubscribed) {
self?.scrollToTop = Date()
if self?.isSubscribed == false && user.isSubscribed {
if self?.scrollToTop == nil {
self?.scrollToTop = Date()
}
if presentationPoint != nil {
self?.dismiss()
}
}
self?.isSubscribed = user.isSubscribed
self?.subscriptionPlan = user.purchased?.subscriptionPlan
self?.showHourglassPromo = user.purchased?.subscriptionPlan?.isEligableForHourglassPromo == true

}).start()

if presentationPoint != nil {
availableSubscriptions.remove(at: 1)
availableSubscriptions.remove(at: 1)
}

disposable.inner.add(inventoryRepository.getLatestMysteryGear().on(value: { gear in
self.mysteryGear = gear
disposable.inner.add(inventoryRepository.getLatestMysteryGear().on(value: {[weak self] gear in
self?.mysteryGear = gear
}).start())

disposable.inner.add(inventoryRepository.getLatestMysteryGearSet().on(value: { set in
self.mysteryGearSet = set
disposable.inner.add(inventoryRepository.getLatestMysteryGearSet().on(value: {[weak self] set in
self?.mysteryGearSet = set
}).start())

retrieveProductList()
Expand Down Expand Up @@ -161,41 +165,9 @@ class SubscriptionViewModel: BaseSubscriptionViewModel {
withAnimation {
isSubscribing = true
}
SwiftyStoreKit.purchaseProduct(selectedSubscription, atomically: false) { result in
PurchaseHandler.shared.purchaseSubscription(selectedSubscription) {[weak self] _ in
withAnimation {
self.isSubscribing = false
}
switch result {
case .success(let product):
self.verifyAndSubscribe(product)
logger.log("Purchase Success: \(product.productId)")
case .error(let error):
Analytics.logEvent("purchase_failed", parameters: ["error": error.localizedDescription, "code": error.errorCode])

logger.log("Purchase Failed: \(error)", level: .error)
case .deferred:
return
}
}
}

func verifyAndSubscribe(_ product: PurchaseDetails) {
SwiftyStoreKit.verifyReceipt(using: appleValidator, forceRefresh: true) { result in
switch result {
case .success(let receipt):
// Verify the purchase of a Subscription
if self.isValidSubscription(product.productId, receipt: receipt) {
self.activateSubscription(product.productId, receipt: receipt) { status in
if status {
if product.needsFinishTransaction {
SwiftyStoreKit.finishTransaction(product.transaction)
}
}
self.dismiss()
}
}
case .error(let error):
logger.log("Receipt verification failed: \(error)", level: .error)
self?.isSubscribing = false
}
}
}
Expand Down Expand Up @@ -229,22 +201,7 @@ class SubscriptionViewModel: BaseSubscriptionViewModel {
return false
}
}

func activateSubscription(_ identifier: String, receipt: ReceiptInfo, completion: @escaping (Bool) -> Void) {
if let lastReceipt = receipt["latest_receipt"] as? String {
userRepository.subscribe(sku: identifier, receipt: lastReceipt).observeResult { (result) in
switch result {
case .success:
completion(true)
self.isSubscribed = true
self.scrollToTop = Date()
case .failure:
completion(false)
}
}
}
}


func checkForExistingSubscription() {
isRestoringPurchase = true
SwiftyStoreKit.verifyReceipt(using: self.appleValidator, forceRefresh: true) { result in
Expand All @@ -257,11 +214,7 @@ class SubscriptionViewModel: BaseSubscriptionViewModel {
for purchase in purchases {
if let identifier = purchase["product_id"] as? String {
if self.isValidSubscription(identifier, receipt: verifiedReceipt) {
self.activateSubscription(identifier, receipt: verifiedReceipt) {status in
if status {
return
}
}
PurchaseHandler.shared.activateSubscription(identifier, receipt: verifiedReceipt, completion: { _ in })
}
}
}
Expand Down Expand Up @@ -319,7 +272,8 @@ struct SubscriptionBenefitListView: View {
if presentationPoint != .armoire {
SubscriptionBenefitView(icon: Image(Asset.subBenefitsArmoire.name), title: Text(L10n.Subscription.infoArmoireTitle), description: Text(L10n.Subscription.infoArmoireDescription))
}
SubscriptionBenefitView(icon: Image(Asset.subBenefitDrops.name), title: Text(L10n.subscriptionInfo5Title), description: Text(L10n.subscriptionInfo5Description)).padding(.bottom, 16)
SubscriptionBenefitView(icon: Image(Asset.subBenefitDrops.name), title: Text(L10n.subscriptionInfo5Title), description: Text(L10n.subscriptionInfo5Description))
.padding(.bottom, 16)
}
}

Expand Down Expand Up @@ -416,10 +370,12 @@ struct SubscriptionPage: View {
.frame(width: reader.size.width * (CGFloat(viewModel.subscriptionPlan?.gemCapTotal ?? 0) / 50.0), height: 8)
}
}
.frame(height: 8)
.padding(.top, 8)
.padding(.horizontal, 41)
}
}.padding(.bottom, 12)
}
.padding(.bottom, 16)
}
SubscriptionBenefitListView(presentationPoint: viewModel.presentationPoint, mysteryGear: viewModel.mysteryGear, mysteryGearSet: viewModel.mysteryGearSet)
.padding(.horizontal, 24)
Expand Down
70 changes: 65 additions & 5 deletions HabitRPG/Utilities/PurchaseHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,39 @@ class PurchaseHandler: NSObject, SKPaymentTransactionObserver {
}
}

func purchaseSubscription(_ identifier: String, completion: @escaping (Bool) -> Void) {
SwiftyStoreKit.purchaseProduct(identifier, atomically: false) { result in
switch result {
case .success(let product):
SwiftyStoreKit.verifyReceipt(using: self.appleValidator) { verificationResult in
switch verificationResult {
case .success(let receipt):
if self.isValidSubscription(identifier, receipt: receipt) == true {
self.activateSubscription(identifier, receipt: receipt) { status in
if status {
SwiftyStoreKit.finishTransaction(product.transaction)
}
}
} else {
SwiftyStoreKit.finishTransaction(product.transaction)
}
case .error(let error):
if error.localizedDescription.contains("Code: 1") {
return
}
self.handle(error: error)
}
}
case .error(let error):
self.handle(error: error)
completion(false)
case .deferred:
completion(false)
return
}
}
}

func verifyPurchase(_ product: PurchaseDetails) {
SwiftyStoreKit.fetchReceipt(forceRefresh: false) { result in
switch result {
Expand Down Expand Up @@ -210,7 +243,7 @@ class PurchaseHandler: NSObject, SKPaymentTransactionObserver {
self?.pendingGifts.removeValue(forKey: identifier)
}
completion(true)
self?.userRepository.retrieveUser().observeCompleted {}
self?.userRepository.retrieveUser(forced: true).observeCompleted {}
} else {
completion(false)
}
Expand All @@ -229,7 +262,7 @@ class PurchaseHandler: NSObject, SKPaymentTransactionObserver {
if result != nil {
self?.pendingGifts.removeValue(forKey: identifier)
completion(true)
self?.userRepository.retrieveUser().observeCompleted {}
self?.userRepository.retrieveUser(forced: true).observeCompleted {}
} else {
completion(false)
}
Expand All @@ -240,9 +273,17 @@ class PurchaseHandler: NSObject, SKPaymentTransactionObserver {
return PurchaseHandler.IAPIdentifiers.contains(identifier)
}

private var isActivatingSubscription = false
func activateSubscription(_ identifier: String, receipt: ReceiptInfo, completion: @escaping (Bool) -> Void) {
if isActivatingSubscription {
return
}
if let lastReceipt = receipt["latest_receipt"] as? String {
userRepository.subscribe(sku: identifier, receipt: lastReceipt).observeResult { (result) in
isActivatingSubscription = true
userRepository.subscribe(sku: identifier, receipt: lastReceipt).on(completed: {
self.userRepository.retrieveUser(forced: true).observeCompleted {}
}).observeResult { (result) in
self.isActivatingSubscription = false
switch result {
case .success:
completion(true)
Expand Down Expand Up @@ -293,7 +334,7 @@ class PurchaseHandler: NSObject, SKPaymentTransactionObserver {
}
}

private func applySubscription(transaction: SKPaymentTransaction) {
func applySubscription(transaction: SKPaymentTransaction) {
if SwiftyStoreKit.localReceiptData == nil {
return
}
Expand Down Expand Up @@ -336,7 +377,26 @@ class PurchaseHandler: NSObject, SKPaymentTransactionObserver {
case .expired(expiryDate: _, items: let items):
self.processItemsForCancellation(items: items, searchedID: searchedID)
default:
return
if #available(iOS 15.0, *) {
Task {
do {
let statuses = try await Product.SubscriptionInfo.status(for: "20345996")
for status in statuses {
guard case .verified(let renewalInfo) = status.renewalInfo else {
continue
}
let isCancelled = renewalInfo.expirationReason != nil && renewalInfo.expirationReason != .billingError
if !renewalInfo.willAutoRenew || isCancelled || (renewalInfo.expirationReason == .billingError && !renewalInfo.isInBillingRetry) {
self.userRepository.cancelSubscription().observeCompleted {
self.wasSubscriptionCancelled = true
}
}
}
} catch let error {
print(error)
}
}
}
}
case .error:
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ import ReactiveSwift
public class CancelSubscribeCall: ResponseObjectCall<EmptyResponseProtocol, APIEmptyResponse> {
public init() {
super.init(httpMethod: .GET, endpoint: "iap/ios/subscribe/cancel")
customErrorHandler = PrintNetworkErrorHandler()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ public class ContentLocalRepository: BaseLocalRepository {
content.customizations.forEach({ (customization) in
newObjects.append(RealmCustomization(customization))
})
content.mystery.forEach({ mysterySet in
newObjects.append(RealmGearSet(mysterySet))
})

saveMysteryItem()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,7 @@ public extension SubscriptionPlanProtocol {
}

var monthsUntilNextHourglass: Int {
if isMonthlyRenewal {
return 3 - perkMonthCount
} else {
return (consecutive?.offset ?? 0) + 1
}
return 1
}

var isEligableForHourglassPromo: Bool {
Expand All @@ -93,7 +89,7 @@ public extension SubscriptionPlanProtocol {
let now = Date()
let calendar = Calendar.current
while (startDate < now) {
var newDate = calendar.date(byAdding: .month, value: interval, to: startDate)
let newDate = calendar.date(byAdding: .month, value: interval, to: startDate)
if let date = newDate {
startDate = date
} else {
Expand Down

0 comments on commit bc20317

Please sign in to comment.