1
+ #if ( UNITY_IOS || UNITY_ANDROID )
2
+ #if MODIO_IN_APP_PURCHASING
3
+
4
+ using System ;
5
+ using System . Collections . Generic ;
6
+ using ModIO ;
7
+ using Plugins . mod . io . Platform . Mobile ;
8
+ using Unity . Services . Core ;
9
+ using Unity . Services . Core . Environments ;
10
+ using UnityEngine ;
11
+ using UnityEngine . Purchasing ;
12
+ using UnityEngine . Purchasing . Extension ;
13
+ using Logger = ModIO . Implementation . Logger ;
14
+ using LogLevel = ModIO . LogLevel ;
15
+
16
+ namespace External
17
+ {
18
+ /// <summary>
19
+ /// An Example of how to use the Unity Purchasing module for android and ios devices.
20
+ /// </summary>
21
+ public class MobilePurchasingExample : MonoBehaviour , IDetailedStoreListener
22
+ {
23
+ static IStoreController storeController ;
24
+ static IExtensionProvider storeExtensionProvider ;
25
+ List < ProductCatalogItem > catalogProducts ;
26
+
27
+ public async void Awake ( )
28
+ {
29
+ InitializationOptions options = new InitializationOptions ( ) ;
30
+
31
+ // Set environment based on build type
32
+ #if UNITY_EDITOR || DEVELOPMENT_BUILD
33
+ options . SetEnvironmentName ( "test" ) ;
34
+ #else
35
+ options . SetEnvironmentName ( "production" ) ;
36
+ #endif
37
+ Logger . Log ( LogLevel . Verbose , "Initializing Unity Services" ) ;
38
+ await UnityServices . InitializeAsync ( options ) ;
39
+ }
40
+
41
+ public void Start ( )
42
+ {
43
+ ResourceRequest operation = Resources . LoadAsync < TextAsset > ( "IAPProductCatalog" ) ;
44
+ operation . completed += HandleIAPCatalogLoaded ;
45
+ Logger . Log ( LogLevel . Verbose , "Initialized Unity Services" ) ;
46
+ }
47
+
48
+ bool IsInitialized ( )
49
+ {
50
+ return storeController != null && storeExtensionProvider != null ;
51
+ }
52
+
53
+ //Converts items from the IAP catalog for use by the Unity Purchasing module
54
+ void HandleIAPCatalogLoaded ( AsyncOperation operation )
55
+ {
56
+ ResourceRequest request = operation as ResourceRequest ;
57
+ Logger . Log ( LogLevel . Verbose , $ "Loaded Asset: { request ? . asset } ") ;
58
+
59
+ var catalog = JsonUtility . FromJson < ProductCatalog > ( ( request ? . asset as TextAsset ) ? . text ) ;
60
+ if ( catalog . allProducts is List < ProductCatalogItem > productList )
61
+ {
62
+ catalogProducts = productList ;
63
+ }
64
+ else
65
+ {
66
+ Logger . Log ( LogLevel . Error , "Catalog data is corrupted!" ) ;
67
+ return ;
68
+ }
69
+
70
+ Logger . Log ( LogLevel . Verbose , $ "Loaded Catalog with { catalog . allProducts . Count } items.") ;
71
+ ConfigurationBuilder builder ;
72
+ #if UNITY_IOS
73
+ builder = ConfigurationBuilder . Instance ( StandardPurchasingModule . Instance ( AppStore . AppleAppStore ) ) ;
74
+ #elif UNITY_ANDROID
75
+ builder = ConfigurationBuilder . Instance ( StandardPurchasingModule . Instance ( AppStore . GooglePlay ) ) ;
76
+ #else
77
+ builder = ConfigurationBuilder . Instance ( StandardPurchasingModule . Instance ( ) ) ;
78
+ #endif
79
+
80
+ foreach ( var p in catalog . allProducts )
81
+ {
82
+ Logger . Log ( LogLevel . Verbose , $ "Added product { p . id } to builder.") ;
83
+ builder . AddProduct ( p . id , p . type ) ;
84
+ }
85
+
86
+ Logger . Log ( LogLevel . Verbose , $ "UnityPurchasing Initialize called") ;
87
+ UnityPurchasing . Initialize ( this , builder ) ;
88
+ }
89
+
90
+
91
+ // Method called when store initialization fails
92
+
93
+ public void OnInitialized ( IStoreController controller , IExtensionProvider extensions )
94
+ {
95
+ storeController = controller ;
96
+ storeExtensionProvider = extensions ;
97
+ Logger . Log ( LogLevel . Verbose , $ "OnInitialized IAP Success") ;
98
+ }
99
+
100
+ // Overloaded method called when store initialization fails with message
101
+ public void OnInitializeFailed ( InitializationFailureReason error )
102
+ {
103
+ Logger . Log ( LogLevel . Error , $ "OnInitialize IAP Failed - { error } ") ;
104
+ }
105
+
106
+ public void OnInitializeFailed ( InitializationFailureReason error , string message )
107
+ {
108
+ Logger . Log ( LogLevel . Error , $ "OnInitialize IAP Failed - { error } : { message } ") ;
109
+ }
110
+
111
+ public void GetWalletBalance ( )
112
+ {
113
+ if ( ! IsInitialized ( ) )
114
+ {
115
+ Debug . LogWarning ( "IAPManager is not initialized." ) ;
116
+ return ;
117
+ }
118
+
119
+ //We must create a wallet before a user can sync
120
+ ModIOUnity . GetUserWalletBalance ( balanceResult =>
121
+ {
122
+ if ( balanceResult . result . Succeeded ( ) )
123
+ {
124
+ Logger . Log ( LogLevel . Verbose , $ "WalletBalance is { balanceResult . value . balance } ") ;
125
+ }
126
+ } ) ;
127
+ }
128
+
129
+ //attached to a button to initiate a token purchase
130
+ public void Buy1000Tokens ( )
131
+ {
132
+ if ( ! IsInitialized ( ) )
133
+ {
134
+ Debug . LogWarning ( "IAPManager is not initialized." ) ;
135
+ return ;
136
+ }
137
+
138
+ if ( catalogProducts . Count == 0 )
139
+ {
140
+ Logger . Log ( LogLevel . Verbose , "No products found in catalog!" ) ;
141
+ return ;
142
+ }
143
+
144
+ string id = catalogProducts [ 0 ] . id ;
145
+ Product product = storeController . products . WithID ( id ) ;
146
+ if ( product != null && product . availableToPurchase )
147
+ storeController . InitiatePurchase ( product ) ;
148
+ }
149
+
150
+ // Method called when InitiatePurchase is completed without an error
151
+ public PurchaseProcessingResult ProcessPurchase ( PurchaseEventArgs args )
152
+ {
153
+ Logger . Log ( LogLevel . Verbose , "ProcessPurchase Called" ) ;
154
+ if ( catalogProducts . Count == 0 )
155
+ {
156
+ Logger . Log ( LogLevel . Verbose , "No products found in catalog!" ) ;
157
+ return PurchaseProcessingResult . Pending ;
158
+ }
159
+
160
+ if ( String . Equals ( args . purchasedProduct . definition . id , catalogProducts [ 0 ] . id , StringComparison . Ordinal ) )
161
+ {
162
+ //Save the receipt for processing. This also calls sync entitlements.
163
+ MobilePurchaseHelper . QueuePurchase ( args . purchasedProduct , ( ) => storeController . ConfirmPendingPurchase ( args . purchasedProduct ) ) ;
164
+ Logger . Log ( LogLevel . Verbose , string . Format ( "ProcessPurchase: PASS. Product: '{0}'" , args . purchasedProduct . definition . id ) ) ;
165
+ }
166
+ else
167
+ {
168
+ Logger . Log ( LogLevel . Verbose , string . Format ( "ProcessPurchase: FAIL. Unrecognized product: '{0}'" , args . purchasedProduct . definition . id ) ) ;
169
+ }
170
+
171
+ return PurchaseProcessingResult . Pending ;
172
+ }
173
+
174
+ // Method called when purchase fails
175
+ public void OnPurchaseFailed ( Product product , PurchaseFailureReason failureReason )
176
+ {
177
+ Logger . Log ( LogLevel . Error , $ "Purchase Failed - { product . definition . id } : { failureReason } ") ;
178
+ }
179
+
180
+ // Overloaded method called when purchase fails with description
181
+ public void OnPurchaseFailed ( Product product , PurchaseFailureDescription failureDescription )
182
+ {
183
+ Logger . Log ( LogLevel . Error , $ "Purchase Failed - { product . definition . id } : { failureDescription . message } ") ;
184
+ }
185
+ }
186
+ }
187
+ #endif
188
+ #endif
0 commit comments