Skip to content

Commit

Permalink
Merge pull request #335 from qonversion/feature/promoOffer
Browse files Browse the repository at this point in the history
Feature/promo offer
  • Loading branch information
suriksarkisyan authored Nov 21, 2024
2 parents e0dfe99 + 2f5bf64 commit d782998
Show file tree
Hide file tree
Showing 12 changed files with 158 additions and 20 deletions.
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ repositories {
dependencies {
//noinspection GradleDynamicVersion
implementation 'com.facebook.react:react-native:+' // From node_modules
implementation "io.qonversion.sandwich:sandwich:5.1.7"
implementation "io.qonversion.sandwich:sandwich:5.2.0"
}

afterEvaluate { project ->
Expand Down
22 changes: 11 additions & 11 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,11 @@ PODS:
- glog (0.3.5)
- libevent (2.1.12)
- OpenSSL-Universal (1.1.1100)
- Qonversion (5.12.3):
- Qonversion/Main (= 5.12.3)
- Qonversion/Main (5.12.3)
- QonversionSandwich (5.1.5):
- Qonversion (= 5.12.3)
- Qonversion (5.13.0):
- Qonversion/Main (= 5.13.0)
- Qonversion/Main (5.13.0)
- QonversionSandwich (5.2.0):
- Qonversion (= 5.13.0)
- RCT-Folly (2021.07.22.00):
- boost
- DoubleConversion
Expand Down Expand Up @@ -289,8 +289,8 @@ PODS:
- React-jsinspector (0.70.7)
- React-logger (0.70.7):
- glog
- react-native-qonversion (8.1.3):
- QonversionSandwich (= 5.1.5)
- react-native-qonversion (8.1.5):
- QonversionSandwich (= 5.2.0)
- React
- React-perflogger (0.70.7)
- React-RCTActionSheet (0.70.7):
Expand Down Expand Up @@ -535,8 +535,8 @@ SPEC CHECKSUMS:
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
Qonversion: d1a0c6aeff9ba9772ed5020899c2b13422f6aff1
QonversionSandwich: 427599ed4e5c655a63eba1cf029647e49a12a457
Qonversion: 37addeba74c5b328de9e1173b580c971b6d764ec
QonversionSandwich: 3ffa118b7214ebd2dcd3f3a1a0a33a39f9c48c8e
RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda
RCTRequired: 837880d26ec119e105317dc28a456f3016bf16d1
RCTTypeSafety: 5c854c04c3383cab04f404e25d408ed52124b300
Expand All @@ -551,7 +551,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: e95cdd036e7947ddf87f3049319ac3064deb76b5
React-jsinspector: 1c34fea1868136ecde647bc11fae9266d4143693
React-logger: e9f407f9fdf3f3ce7749ae6f88affe63e8446019
react-native-qonversion: c848653c8fd84f59b0a43f5ea871785fb919c145
react-native-qonversion: 2149025c324df0b9cfb0b03cd73833907259e160
React-perflogger: 52a94f38c19a518d05726624b49bfc192639374d
React-RCTActionSheet: 7b89fe64a852bc3ae39b91dbd142ef09931ef3f7
React-RCTAnimation: ad84bfbf8c5f6f77e65092d0c2b0506b80b5cf99
Expand All @@ -571,4 +571,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: f9772353ed0452b7cd7aaeb49261dbd07dd4af10

COCOAPODS: 1.13.0
COCOAPODS: 1.15.2
10 changes: 8 additions & 2 deletions ios/RNQonversion.m
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,14 @@ + (BOOL)requiresMainQueueSetup
[_qonversionSandwich syncStoreKit2Purchases];
}

RCT_EXPORT_METHOD(purchase:(NSString *)productId quantity:(NSInteger)quantity contextKeys:(NSArray *)contextKeys completion:(RCTResponseSenderBlock)completion rejecter:(RCTPromiseRejectBlock)reject) {
[_qonversionSandwich purchase:productId quantity:quantity contextKeys:contextKeys completion:^(NSDictionary<NSString *,id> * _Nullable result, SandwichError * _Nullable error) {
RCT_EXPORT_METHOD(getPromotionalOffer:(NSString *)productId discountId:(NSString *)discountId completion:(RCTResponseSenderBlock)completion rejecter:(RCTPromiseRejectBlock)reject) {
[_qonversionSandwich getPromotionalOffer:productId productDiscountId:discountId completion:^(NSDictionary<NSString *,id> * _Nullable result, SandwichError * _Nullable error) {
[self handleResult:result error:error completion:completion rejecter:reject];
}];
}

RCT_EXPORT_METHOD(purchase:(NSString *)productId quantity:(NSInteger)quantity contextKeys:(NSArray *)contextKeys promoOffer:(NSDictionary *)promoOffer completion:(RCTResponseSenderBlock)completion rejecter:(RCTPromiseRejectBlock)reject) {
[_qonversionSandwich purchase:productId quantity:quantity contextKeys:contextKeys promoOffer:promoOffer completion:^(NSDictionary<NSString *,id> * _Nullable result, SandwichError * _Nullable error) {
[self handleResult:result error:error completion:completion rejecter:reject];
}];
}
Expand Down
2 changes: 1 addition & 1 deletion react-native-qonversion.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ Pod::Spec.new do |s|
s.requires_arc = true

s.dependency "React"
s.dependency "QonversionSandwich", "5.1.7"
s.dependency "QonversionSandwich", "5.2.0"
end
15 changes: 15 additions & 0 deletions src/QonversionApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import UserProperties from './dto/UserProperties';
import PurchaseModel from './dto/PurchaseModel';
import PurchaseUpdateModel from './dto/PurchaseUpdateModel';
import PurchaseOptions from "./dto/PurchaseOptions";
import SKProductDiscount from './dto/storeProducts/SKProductDiscount';
import PromotionalOffer from './dto/PromotionalOffer';

interface QonversionApi {

Expand All @@ -33,6 +35,19 @@ interface QonversionApi {
*/
isFallbackFileAccessible(): Promise<Boolean>;

/**
* iOS only.
* Retrieve the promotional offer for the product if it exists.
* Make sure to call this function before displaying product details to the user.
* The generated signature for the promotional offer is valid for a single transaction.
* If the purchase fails, you need to call this function again to obtain a new promotional offer signature.
* Use this signature to complete the purchase through the purchase function, along with the purchase options object.
* @param product - product you want to purchase.
* @param discount - discount to create promotional offer signature.
* @returns the promise with the PromotionalOffer.
*/
getPromotionalOffer(product: Product, discount: SKProductDiscount): Promise<PromotionalOffer | null>;

/**
* Make a purchase and validate it through server-to-server using Qonversion's Backend
* @param product product to purchase
Expand Down
17 changes: 17 additions & 0 deletions src/dto/PromotionalOffer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import SKProductDiscount from './storeProducts/SKProductDiscount';
import SKPaymentDiscount from './storeProducts/SKPaymentDiscount';

class PromotionalOffer {
public readonly productDiscount: SKProductDiscount;
public readonly paymentDiscount: SKPaymentDiscount;

constructor (
productDiscount: SKProductDiscount,
paymentDiscount: SKPaymentDiscount
) {
this.productDiscount = productDiscount;
this.paymentDiscount = paymentDiscount;
}
}

export default PromotionalOffer;
6 changes: 5 additions & 1 deletion src/dto/PurchaseOptions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Product from "./Product";
import {PurchaseUpdatePolicy} from "./enums";
import PromotionalOffer from './PromotionalOffer';

class PurchaseOptions {
public readonly offerId: string | null;
Expand All @@ -8,21 +9,24 @@ class PurchaseOptions {
public readonly updatePolicy: PurchaseUpdatePolicy | null;
public readonly contextKeys: string[] | null;
public readonly quantity: number;
public readonly promotionalOffer: PromotionalOffer | null;

constructor (
offerId: string | null,
applyOffer: boolean,
oldProduct: Product | null,
updatePolicy: PurchaseUpdatePolicy | null,
contextKeys: string[] | null,
quantity: number
quantity: number,
promotionalOffer: PromotionalOffer | null
) {
this.offerId = offerId;
this.applyOffer = applyOffer;
this.oldProduct = oldProduct;
this.updatePolicy = updatePolicy;
this.contextKeys = contextKeys;
this.quantity = quantity;
this.promotionalOffer = promotionalOffer;
}
}

Expand Down
22 changes: 21 additions & 1 deletion src/dto/PurchaseOptionsBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Product from "./Product";
import {PurchaseUpdatePolicy} from "./enums";
import ProductOfferDetails from "./storeProducts/ProductOfferDetails";
import PurchaseOptions from "./PurchaseOptions";
import PromotionalOffer from "./PromotionalOffer";

class PurchaseOptionsBuilder {
private offerId: string | null = null;
Expand All @@ -10,6 +11,7 @@ class PurchaseOptionsBuilder {
private updatePolicy: PurchaseUpdatePolicy | null = null;
private contextKeys: string[] | null = null;
private quantity: number = 1;
private promoOffer: PromotionalOffer | null = null;

/**
* iOS only.
Expand Down Expand Up @@ -95,12 +97,30 @@ class PurchaseOptionsBuilder {
return this;
}

/**
* Set the promotional offer details.
*
* @param promoOffer promotional offer details.
* @return builder instance for chain calls.
*/
setPromotionalOffer(promoOffer: PromotionalOffer): PurchaseOptionsBuilder {
this.promoOffer = promoOffer;
return this;
}

/**
* Generate {@link PurchaseOptions} instance with all the provided options.
* @return the complete {@link PurchaseOptions} instance.
*/
build(): PurchaseOptions {
return new PurchaseOptions(this.offerId, this.applyOffer, this.oldProduct, this.updatePolicy, this.contextKeys, this.quantity)
return new PurchaseOptions(
this.offerId,
this.applyOffer,
this.oldProduct,
this.updatePolicy,
this.contextKeys,
this.quantity,
this.promoOffer);
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/dto/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Transaction {
expirationDate?: Date;
transactionRevocationDate?: Date;
offerCode?: string;
promoOfferId?: string;

constructor(
originalTransactionId: string,
Expand All @@ -21,6 +22,7 @@ class Transaction {
expirationTimestamp: number | undefined,
transactionRevocationTimestamp: number | undefined,
offerCode: string | undefined,
promoOfferId: string | undefined,
) {
this.originalTransactionId = originalTransactionId;
this.transactionId = transactionId;
Expand All @@ -31,6 +33,7 @@ class Transaction {
this.expirationDate = expirationTimestamp ? new Date(expirationTimestamp) : undefined;
this.transactionRevocationDate = transactionRevocationTimestamp ? new Date(transactionRevocationTimestamp) : undefined;
this.offerCode = offerCode;
this.promoOfferId = promoOfferId;
}
}

Expand Down
23 changes: 23 additions & 0 deletions src/dto/storeProducts/SKPaymentDiscount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class SKPaymentDiscount {
identifier: string;
keyIdentifier: string;
nonce: string;
signature: string;
timestamp: number;

constructor (
identifier: string,
keyIdentifier: string,
nonce: string,
signature: string,
timestamp: number,
) {
this.identifier = identifier;
this.keyIdentifier = keyIdentifier;
this.nonce = nonce;
this.signature = signature;
this.timestamp = timestamp;
}
}

export default SKPaymentDiscount;
30 changes: 30 additions & 0 deletions src/internal/Mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ import ProductInAppDetails from "../dto/storeProducts/ProductInAppDetails";
import ProductPrice from "../dto/storeProducts/ProductPrice";
import ProductPricingPhase from "../dto/storeProducts/ProductPricingPhase";
import ProductInstallmentPlanDetails from '../dto/storeProducts/ProductInstallmentPlanDetails';
import PromotionalOffer from '../dto/PromotionalOffer';
import SKPaymentDiscount from '../dto/storeProducts/SKPaymentDiscount';

type QProduct = {
id: string;
Expand Down Expand Up @@ -107,6 +109,11 @@ type QProductInstallmentPlanDetails = {
subsequentCommitmentPaymentsCount: number;
}

type QPromotionalOffer = {
productDiscount: QProductDiscount,
paymentDiscount: QPaymentDiscount,
}

type QProductOfferDetails = {
basePlanId: string,
offerId?: string | null,
Expand Down Expand Up @@ -188,6 +195,14 @@ type QProductDiscount = {
priceLocale: QLocale;
};

type QPaymentDiscount = {
identifier: string;
keyIdentifier: string;
nonce: string;
signature: string;
timestamp: number;
};

type QLocale = {
currencySymbol: string | null;
currencyCode: string | null;
Expand Down Expand Up @@ -222,6 +237,7 @@ type QTransaction = {
environment: string;
ownershipType: string;
type: string;
promoOfferId: string;
}

type QOfferings = {
Expand Down Expand Up @@ -287,6 +303,15 @@ type QUserProperties = {
const priceMicrosRatio = 1000000;

class Mapper {
static convertPromoOffer(
promoOffer: QPromotionalOffer
): PromotionalOffer {
const productDiscount = this.convertProductDiscount(promoOffer.productDiscount);
const paymentDiscount = this.convertPaymentDiscount(promoOffer.paymentDiscount);

return new PromotionalOffer(productDiscount, paymentDiscount);
}

static convertEntitlements(
entitlements: Record<string, QEntitlement> | null | undefined
): Map<string, Entitlement> {
Expand Down Expand Up @@ -365,6 +390,7 @@ class Mapper {
transaction.expirationTimestamp,
transaction.transactionRevocationTimestamp,
transaction.offerCode,
transaction.promoOfferId,
);
}

Expand Down Expand Up @@ -908,6 +934,10 @@ class Mapper {
);
}

static convertPaymentDiscount(discount: QPaymentDiscount): SKPaymentDiscount {
return new SKPaymentDiscount(discount.identifier, discount.keyIdentifier, discount.nonce, discount.signature, discount.timestamp)
}

static convertProductDiscount(discount: QProductDiscount): SKProductDiscount {
let subscriptionPeriod: SKSubscriptionPeriod | undefined = undefined;
if (discount.subscriptionPeriod != null) {
Expand Down
Loading

0 comments on commit d782998

Please sign in to comment.