Skip to content

Commit a77b586

Browse files
authored
In-App Purchase (#718)
* Create encrypted file for In-App Purchases product identifiers * Retrieve store products via IAPProductsService * Synchronize fetchProducts(productIdentifiers:) * Fix param name * Validate receipts * Add payments service * Buy course * Support course purchases API APPS-2768 * Update validate receipt method signature * Cache ongoing payments * Update product ids * Use course payment payload data * Add default course payment request handler * Fix fetch course in default handler * Add error descriptions * Handle payment states in course info * Present course info in course list on paid course click * Fetch IAP localized price * Prefetch products on app launch * Clean up * Merge Core Data models * Update product ids
1 parent 1991e2c commit a77b586

34 files changed

+2010
-53
lines changed

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ StepicTests/TestInfo.plist filter=git-crypt diff=git-crypt
44
Stepic/Auth.plist filter=git-crypt diff=git-crypt
55
Stepic/GoogleService-Info.plist filter=git-crypt diff=git-crypt
66
Stepic/Legacy/Model/Tokens/Tokens.plist filter=git-crypt diff=git-crypt
7+
Stepic/Sources/Frameworks/InAppPurchases/IAPProductIDs.plist filter=git-crypt diff=git-crypt
8+
Stepic/Sources/Frameworks/InAppPurchases/IAPPaymentsCache.swift filter=git-crypt diff=git-crypt
79
fastlane/metadata/**/review_information/** filter=git-crypt diff=git-crypt
810
fastlane/Deliverfile filter=git-crypt diff=git-crypt
911
fastlane/Appfile filter=git-crypt diff=git-crypt

Stepic.xcodeproj/project.pbxproj

Lines changed: 69 additions & 23 deletions
Large diffs are not rendered by default.

Stepic/Legacy/AppDelegate.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
123123

124124
ApplicationThemeService().registerDefaultTheme()
125125

126+
IAPService.shared.startObservingPayments()
127+
126128
return true
127129
}
128130

@@ -137,13 +139,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
137139
NotificationsBadgesManager.shared.set(number: application.applicationIconBadgeNumber)
138140
self.notificationsService.removeRetentionNotifications()
139141
self.userCoursesObserver.startObserving()
142+
IAPService.shared.prefetchProducts()
140143
}
141144

142145
func applicationWillResignActive(_ application: UIApplication) {
143146
self.notificationsService.scheduleRetentionNotifications()
144147
self.userCoursesObserver.stopObserving()
145148
}
146149

150+
func applicationWillTerminate(_ application: UIApplication) {
151+
IAPService.shared.stopObservingPayments()
152+
}
153+
147154
// MARK: - Downloading Data in the Background
148155

149156
func application(

Stepic/Legacy/Model/Entities/Course/Course+CoreDataProperties.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ extension Course {
5454

5555
@NSManaged var managedIsPaid: NSNumber?
5656
@NSManaged var managedDisplayPrice: String?
57+
@NSManaged var managedPriceTier: NSNumber?
5758

5859
// MARK: Relationships
5960
@NSManaged var managedAuthors: NSOrderedSet?
@@ -66,6 +67,7 @@ extension Course {
6667
@NSManaged var managedReviewSummary: CourseReviewSummary?
6768
@NSManaged var managedSections: NSOrderedSet?
6869
@NSManaged var managedUserCourse: UserCourse?
70+
@NSManaged var managedCoursePurchases: NSOrderedSet?
6971

7072
static var oldEntity: NSEntityDescription {
7173
NSEntityDescription.entity(forEntityName: "Course", in: CoreDataHelper.shared.context)!
@@ -256,6 +258,15 @@ extension Course {
256258
}
257259
}
258260

261+
var priceTier: Int? {
262+
get {
263+
self.managedPriceTier?.intValue
264+
}
265+
set {
266+
self.managedPriceTier = newValue as NSNumber?
267+
}
268+
}
269+
259270
var readiness: Float? {
260271
set {
261272
self.managedReadiness = newValue as NSNumber?
@@ -490,6 +501,19 @@ extension Course {
490501
}
491502
}
492503

504+
var purchases: [CoursePurchase] {
505+
get {
506+
(self.managedCoursePurchases?.array as? [CoursePurchase]) ?? []
507+
}
508+
set {
509+
self.managedCoursePurchases = NSOrderedSet(array: newValue)
510+
}
511+
}
512+
513+
var isPurchased: Bool {
514+
self.purchases.contains(where: { $0.isActive })
515+
}
516+
493517
var userCourse: UserCourse? {
494518
get {
495519
self.managedUserCourse

Stepic/Legacy/Model/Entities/Course/Course.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ final class Course: NSManagedObject, IDFetchable {
120120
self.languageCode = json[JSONKey.language.rawValue].stringValue
121121
self.isPaid = json[JSONKey.isPaid.rawValue].boolValue
122122
self.displayPrice = json[JSONKey.displayPrice.rawValue].string
123+
self.priceTier = json[JSONKey.priceTier.rawValue].int
123124

124125
self.certificate = json[JSONKey.certificate.rawValue].stringValue
125126
self.certificateRegularThreshold = json[JSONKey.certificateRegularThreshold.rawValue].int
@@ -436,5 +437,6 @@ final class Course: NSManagedObject, IDFetchable {
436437
case isPaid = "is_paid"
437438
case displayPrice = "display_price"
438439
case introVideo = "intro_video"
440+
case priceTier = "price_tier"
439441
}
440442
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import CoreData
2+
import Foundation
3+
4+
extension CoursePurchase {
5+
@NSManaged var managedId: NSNumber?
6+
@NSManaged var managedUserId: NSNumber?
7+
@NSManaged var managedCourseId: NSNumber?
8+
@NSManaged var managedIsActive: NSNumber?
9+
@NSManaged var managedPaymentId: NSNumber?
10+
11+
@NSManaged var managedCourse: Course?
12+
13+
static var oldEntity: NSEntityDescription {
14+
NSEntityDescription.entity(forEntityName: "CoursePurchase", in: CoreDataHelper.shared.context)!
15+
}
16+
17+
static var defaultSortDescriptors: [NSSortDescriptor] {
18+
[NSSortDescriptor(key: #keyPath(managedId), ascending: false)]
19+
}
20+
21+
static var fetchRequest: NSFetchRequest<CoursePurchase> {
22+
NSFetchRequest<CoursePurchase>(entityName: "CoursePurchase")
23+
}
24+
25+
convenience init() {
26+
self.init(entity: Self.oldEntity, insertInto: CoreDataHelper.shared.context)
27+
}
28+
29+
var id: Int {
30+
get {
31+
self.managedId?.intValue ?? 0
32+
}
33+
set {
34+
self.managedId = NSNumber(value: newValue)
35+
}
36+
}
37+
38+
var userID: Int {
39+
get {
40+
self.managedUserId?.intValue ?? 0
41+
}
42+
set {
43+
self.managedUserId = NSNumber(value: newValue)
44+
}
45+
}
46+
47+
var courseID: Int {
48+
get {
49+
self.managedCourseId?.intValue ?? 0
50+
}
51+
set {
52+
self.managedCourseId = NSNumber(value: newValue)
53+
}
54+
}
55+
56+
var isActive: Bool {
57+
get {
58+
self.managedIsActive?.boolValue ?? false
59+
}
60+
set {
61+
self.managedIsActive = NSNumber(value: newValue)
62+
}
63+
}
64+
65+
var paymentID: Int {
66+
get {
67+
self.managedPaymentId?.intValue ?? 0
68+
}
69+
set {
70+
self.managedPaymentId = NSNumber(value: newValue)
71+
}
72+
}
73+
74+
var course: Course? {
75+
get {
76+
self.managedCourse
77+
}
78+
set {
79+
self.managedCourse = newValue
80+
}
81+
}
82+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import CoreData
2+
import Foundation
3+
import SwiftyJSON
4+
5+
final class CoursePurchase: NSManagedObject, JSONSerializable, IDFetchable {
6+
typealias IdType = Int
7+
8+
required convenience init(json: JSON) {
9+
self.init()
10+
self.update(json: json)
11+
}
12+
13+
func update(json: JSON) {
14+
self.id = json[JSONKey.id.rawValue].intValue
15+
self.userID = json[JSONKey.user.rawValue].intValue
16+
self.courseID = json[JSONKey.course.rawValue].intValue
17+
self.isActive = json[JSONKey.isActive.rawValue].boolValue
18+
self.paymentID = json[JSONKey.payment.rawValue].intValue
19+
}
20+
21+
func hasEqualId(json: JSON) -> Bool {
22+
self.id == json[JSONKey.id.rawValue].int
23+
}
24+
25+
enum JSONKey: String {
26+
case id
27+
case user
28+
case course
29+
case isActive = "is_active"
30+
case payment
31+
}
32+
}

Stepic/Legacy/Model/Model.xcdatamodeld/.xccurrentversion

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
<plist version="1.0">
44
<dict>
55
<key>_XCCurrentVersionName</key>
6-
<string>Model_new_profile_v53.xcdatamodel</string>
6+
<string>Model_course_purchases_v54.xcdatamodel</string>
77
</dict>
88
</plist>

0 commit comments

Comments
 (0)