Skip to content

Commit 1c8796b

Browse files
authored
[FINAL] Subscription offers support (#123)
* Defaults to first subscribtion offer in the discounts array * changes common files to use temporary branch * updated common android * added new functions * uses timestamp instead of identifier * updates gitignore * adds development.md, it's a wip and copied from notion * Adds intro price class * updates common version * fixes tests * updates common * Fixes configure * line breaks * changes remap to export * updates sdk versions * fixes framework location
1 parent 5105a31 commit 1c8796b

File tree

19 files changed

+1295
-72
lines changed

19 files changed

+1295
-72
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,4 @@ example/.vscode/settings.json
6161
example/android/app/.classpath
6262
example/android/app/.project
6363
example/android/app/.settings/org.eclipse.buildship.core.prefs
64+
example/.vscode/

DEVELOPMENT.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Make sure react-native-purchases is not part of the examples package.json
2+
3+
Setup the development by running:
4+
5+
yarn run setup.example
6+
7+
That will link the local package so that changes are automatically applied to the example
8+
9+
---
10+
11+
- Plug a device and run:
12+
`react-native run-android`
13+
or
14+
`react-native run-ios`
15+
16+
---
17+
18+
To edit the iOS code, open the example project with XCode, there should be a subproject there RNPurchases.xcodeproj that can be used to edit the plugin.
19+
If touching common files, make sure you copy them to their repo after editing them, so that changes are not lost when re linking the plugin (since it will download the dependencies again and overwrite your changes).
20+
You can run the project from XCode without having to run `react-native run-ios`, but make sure that if you are touching `.ts` files, you run `npm run build` to compile the plugin.
21+
22+
In Android, the common code is uploaded to the repo. Make sure you commit the changes.
23+
24+
## Common issues
25+
26+
> ReferenceError: Module not registered in graph: /Users/cesardelavega/Development/repos/react-native/react-native-purchases/example/node_modules/@babel/runtime/helpers/get.js
27+
28+
Clean all the node_modules folders and restart the server
29+
30+
---
31+
32+
Make sure it's connected to the same wifi
33+
34+
---
35+
36+
Make sure the Android device doesn't have any app with the same package name you're running
37+
38+
---
39+
40+
Make sure your Android emulator has play services and you're logged in

RNPurchases.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@ Pod::Spec.new do |spec|
1919
spec.exclude_files = "ios/Purchases.framework"
2020

2121
spec.dependency "React"
22-
spec.dependency "Purchases", "~> 3.0.1"
22+
spec.dependency "Purchases", "~> 3.2.1"
2323
end

VERSIONS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
| Version | iOS version | Android version | Common files version |
22
|---------|-------------|-----------------|----------------------|
3+
| 3.0.7 | 3.2.1 | 3.1.0 | 1.0.9 |
34
| 3.0.6 | 3.0.1 | 3.0.4 | 1.0.5 |
45
| 3.0.5 | 3.0.1 | 3.0.4 | 1.0.5 |
56
| 3.0.4 | 3.0.1 | 3.0.4 | 1.0.5 |

__tests__/index.test.js

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ describe("Purchases", () => {
260260
offeringIdentifier: "offering",
261261
});
262262

263-
expect(NativeModules.RNPurchases.purchasePackage).toBeCalledWith("$rc_onemonth", "offering", undefined);
263+
expect(NativeModules.RNPurchases.purchasePackage).toBeCalledWith("$rc_onemonth", "offering", undefined, null);
264264
expect(NativeModules.RNPurchases.purchasePackage).toBeCalledTimes(1);
265265

266266
Purchases.purchasePackage(
@@ -292,7 +292,7 @@ describe("Purchases", () => {
292292
expect(NativeModules.RNPurchases.purchasePackage).toBeCalledWith("$rc_onemonth", "offering", {
293293
oldSKU: "viejo",
294294
prorationMode: Purchases.PRORATION_MODE.DEFERRED
295-
});
295+
}, null);
296296
expect(NativeModules.RNPurchases.purchasePackage).toBeCalledTimes(2);
297297
});
298298

@@ -526,6 +526,125 @@ describe("Purchases", () => {
526526
expect(NativeModules.RNPurchases.checkTrialOrIntroductoryPriceEligibility).toBeCalledWith(["monthly"]);
527527
})
528528

529+
it("getPaymentDiscount works", () => {
530+
const Purchases = require("../index").default;
531+
532+
NativeModules.RNPurchases.getPaymentDiscount.mockResolvedValue(paymentDiscountStub);
533+
534+
const aProduct = {
535+
...productStub,
536+
discounts: [discountStub]
537+
}
538+
539+
Purchases.getPaymentDiscount(aProduct, discountStub)
540+
541+
expect(NativeModules.RNPurchases.getPaymentDiscount).toBeCalledWith(aProduct.identifier, discountStub.identifier);
542+
expect(NativeModules.RNPurchases.getPaymentDiscount).toBeCalledTimes(1);
543+
});
544+
545+
it("getPaymentDiscount returns undefined for Android", async () => {
546+
const Purchases = require("../index").default;
547+
548+
mockPlatform("android");
549+
550+
let paymentDiscount = await Purchases.getPaymentDiscount(productStub, discountStub);
551+
552+
expect(paymentDiscount).toEqual(undefined)
553+
expect(NativeModules.RNPurchases.getPaymentDiscount).toBeCalledTimes(0);
554+
});
555+
556+
it("getPaymentDiscount throws error when null discount", () => {
557+
const Purchases = require("../index").default;
558+
mockPlatform("ios");
559+
560+
expect(() => {
561+
Purchases.getPaymentDiscount(productStub, null)
562+
}).toThrowError();
563+
564+
expect(() => {
565+
Purchases.getPaymentDiscount(productStub)
566+
}).toThrowError();
567+
568+
expect(NativeModules.RNPurchases.getPaymentDiscount).toBeCalledTimes(0);
569+
});
570+
571+
it("purchaseDiscountedProduct works", () => {
572+
const Purchases = require("../index").default;
573+
574+
NativeModules.RNPurchases.purchaseProduct.mockResolvedValue({
575+
purchasedProductIdentifier: "123",
576+
purchaserInfo: purchaserInfoStub
577+
});
578+
579+
const aProduct = {
580+
...productStub,
581+
discounts: [discountStub]
582+
}
583+
584+
Purchases.purchaseDiscountedProduct(aProduct, paymentDiscountStub)
585+
586+
expect(NativeModules.RNPurchases.purchaseProduct).toBeCalledWith(aProduct.identifier, null, null, paymentDiscountStub.timestamp);
587+
expect(NativeModules.RNPurchases.purchaseProduct).toBeCalledTimes(1);
588+
});
589+
590+
it("purchaseDiscountedProduct throws if null or undefined discount", () => {
591+
const Purchases = require("../index").default;
592+
593+
expect(() => {
594+
Purchases.purchaseDiscountedProduct(productStub, null)
595+
}).toThrow();
596+
597+
expect(() => {
598+
Purchases.purchaseDiscountedProduct(productStub)
599+
}).toThrow();
600+
601+
expect(NativeModules.RNPurchases.purchaseProduct).toBeCalledTimes(0);
602+
});
603+
604+
it("purchaseDiscountedPackage works", () => {
605+
const Purchases = require("../index").default;
606+
607+
NativeModules.RNPurchases.purchasePackage.mockResolvedValue({
608+
purchasedProductIdentifier: "123",
609+
purchaserInfo: purchaserInfoStub
610+
});
611+
612+
const aProduct = {
613+
...productStub,
614+
discounts: [discountStub]
615+
}
616+
617+
const aPackage = {
618+
...packagestub,
619+
product: aProduct
620+
}
621+
622+
Purchases.purchaseDiscountedPackage(aPackage, paymentDiscountStub)
623+
624+
expect(NativeModules.RNPurchases.purchasePackage).toBeCalledWith(
625+
aPackage.identifier,
626+
aPackage.offeringIdentifier,
627+
null,
628+
paymentDiscountStub.timestamp
629+
);
630+
expect(NativeModules.RNPurchases.purchasePackage).toBeCalledTimes(1);
631+
632+
});
633+
634+
it("purchaseDiscountedPackage throws if null or undefined discount", () => {
635+
const Purchases = require("../index").default;
636+
637+
expect(() => {
638+
Purchases.purchaseDiscountedPackage(packagestub, null)
639+
}).toThrow();
640+
641+
expect(() => {
642+
Purchases.purchaseDiscountedPackage(packagestub)
643+
}).toThrow();
644+
645+
expect(NativeModules.RNPurchases.purchaseProduct).toBeCalledTimes(0);
646+
});
647+
529648
const mockPlatform = OS => {
530649
jest.resetModules();
531650
jest.doMock("Platform", () => ({OS, select: objs => objs[OS]}));

android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,6 @@ repositories {
3838
dependencies {
3939
//noinspection GradleDynamicVersion
4040
api 'com.facebook.react:react-native:+'
41-
implementation 'com.revenuecat.purchases:purchases:3.0.4'
41+
implementation 'com.revenuecat.purchases:purchases:3.1.0'
4242
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
4343
}

android/src/main/java/com/revenuecat/purchases/common/common.kt

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ fun purchaseProduct(
8787
it.sku == productIdentifier && it.type.equals(type, ignoreCase = true)
8888
}
8989
if (productToBuy != null) {
90-
if (oldSku.isNullOrBlank()) {
90+
if (oldSku == null || oldSku.isBlank()) {
9191
Purchases.sharedInstance.purchaseProductWith(
9292
activity,
9393
productToBuy,
@@ -154,7 +154,7 @@ fun purchasePackage(
154154
it.identifier.equals(packageIdentifier, ignoreCase = true)
155155
}
156156
if (packageToBuy != null) {
157-
if (oldSku.isNullOrBlank()) {
157+
if (oldSku == null || oldSku.isBlank()) {
158158
Purchases.sharedInstance.purchasePackageWith(
159159
activity,
160160
packageToBuy,
@@ -266,6 +266,34 @@ fun checkTrialOrIntroductoryPriceEligibility(
266266
}.toMap()
267267
}
268268

269+
fun invalidatePurchaserInfoCache() {
270+
Purchases.sharedInstance.invalidatePurchaserInfoCache()
271+
}
272+
273+
// region Subscriber Attributes
274+
275+
fun setAttributes(attributes: Map<String, String?>) {
276+
Purchases.sharedInstance.setAttributes(attributes)
277+
}
278+
279+
fun setEmail(email: String?) {
280+
Purchases.sharedInstance.setEmail(email)
281+
}
282+
283+
fun setPhoneNumber(phoneNumber: String?) {
284+
Purchases.sharedInstance.setPhoneNumber(phoneNumber)
285+
}
286+
287+
fun setDisplayName(displayName: String?) {
288+
Purchases.sharedInstance.setDisplayName(displayName)
289+
}
290+
291+
fun setPushToken(fcmToken: String?) {
292+
Purchases.sharedInstance.setPushToken(fcmToken)
293+
}
294+
295+
// region private functions
296+
269297
private fun getMakePurchaseErrorFunction(onResult: OnResult): (PurchasesError, Boolean) -> Unit {
270298
return { error, userCancelled -> onResult.onError(error.map(mapOf("userCancelled" to userCancelled))) }
271299
}

0 commit comments

Comments
 (0)