CampBuddy/Camp.Buddy v2.2.1/Camp_Buddy-2.2.1-pc/renpy/common/00iap.rpy
2025-03-03 23:00:33 +01:00

598 lines
17 KiB
Text

# Copyright 2004-2019 Tom Rothamel <pytom@bishoujo.us>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
init -1500 python in iap:
from store import persistent, Action
import time
background = "black"
class Product(object):
"""
A data object representing a product.
"""
def __init__(self, product, identifier, google, amazon, ios, consumable):
self.product = product
self.identifier = identifier
self.google = google
self.amazon = amazon
self.ios = ios
# None if the item is not purchasable. Otherwise, a string that
# gives the price in the local language.
self.price = None
self.consumable = consumable
class NoneBackend(object):
"""
The IAP backend that is used when IAP is not supported.
"""
def get_store_name(self):
"""
Returns the name of the app store in use, or None if there is
no app store.
"""
return None
def purchase(self, p, interact=True):
"""
Triggers an attempt to purchase the product `p`. Returns true if
the purchase succeeds, and False otherwise.
"""
return False
def restore_purchases(self, interact=True):
"""
Restores purchases found on the server.
`interact`
If true, Ren'Py will pause while waiting for the restore to
occur.
"""
def has_purchased(self, p):
"""
Returns True if `p` has been purchased, and False otherwise.
"""
return False
def consume(self, p):
"""
Attempts to consume a `p`. Returns True if a `p` has been purchased
and consumed, or False if not.
"""
return False
def is_deferred(self, p):
"""
Returns True if the purchase of `p` has been deferred, and False otherwise.
"""
return False
def get_price(self, p):
"""
Returns the price of the item, or None if the item is not
purchasable.
"""
return None
def init(self):
"""
Called at init time to do any initialization required.
"""
return
class AndroidBackend(object):
"""
The IAP backend that is used when IAP is supported.
"""
def __init__(self, store, store_name):
self.store = store
self.store_name = store_name
self.store.clearSKUs()
for p in products.values():
self.store.addSKU(self.identifier(p))
def get_store_name(self):
return self.store_name
def identifier(self, p):
"""
Returns the identifier for a store purchase.
"""
if self.store_name == "amazon":
return p.amazon
else:
return p.google
def wait_for_result(self, interact=True):
"""
Waits for a result.
`interact`
If true, waits interactively. If false, waits using
renpy.pause.
"""
while not self.store.getFinished():
if interact:
renpy.pause(.1)
else:
time.sleep(.1)
def purchase(self, p, interact=True):
identifier = self.identifier(p)
self.store.beginPurchase(identifier)
self.wait_for_result(interact=interact)
def restore_purchases(self, interact=True):
self.store.updatePrices();
self.wait_for_result(interact)
self.store.restorePurchases();
self.wait_for_result(interact)
def has_purchased(self, p):
identifier = self.identifier(p)
return self.store.hasPurchased(identifier)
def consume(self, p):
return False
def is_deferred(self, p):
return False
def get_price(self, p):
identifier = self.identifier(p)
return self.store.getPrice(identifier)
def init(self):
restore(False)
if renpy.renpy.ios:
import pyobjus
IAPHelper = pyobjus.autoclass(b"IAPHelper")
NSMutableArray = pyobjus.autoclass(b"NSMutableArray")
from pyobjus import objc_str, objc_arr
class IOSBackend(object):
def __init__(self):
self.helper = IAPHelper.alloc().init()
identifiers = NSMutableArray.alloc().init()
for p in products.values():
identifiers.addObject_(objc_str(p.ios))
self.helper.productIdentifiers = identifiers
self.validated_products = False
def get_store_name(self):
if renpy.predicting():
return "ios"
if self.helper.canMakePayments():
return "ios"
else:
return None
def set_title(self):
self.helper.dialogTitle = __("Contacting App Store\nPlease Wait...")
def identifier(self, p):
"""
Returns the identifier for a store purchase.
"""
return p.ios
def wait_for_result(self, interact=True):
"""
Waits for a result.
`interact`
If true, waits interactively. If false, waits using
renpy.pause.
"""
while not self.helper.finished:
if interact:
renpy.pause(.1)
else:
import pygame
pygame.event.pump()
time.sleep(.1)
def validate_products(self, interact):
if self.validated_products:
return False
self.helper.validateProductIdentifiers()
self.wait_for_result(interact)
self.validated_products = True
def purchase(self, p, interact=True):
self.set_title()
self.validate_products(interact)
identifier = objc_str(self.identifier(p))
self.helper.beginPurchase_(identifier)
self.wait_for_result(interact=interact)
def restore_purchases(self, interact=True):
self.set_title()
self.validate_products(interact)
self.helper.restorePurchases()
self.wait_for_result(interact)
def has_purchased(self, p):
identifier = objc_str(self.identifier(p))
return self.helper.hasPurchased_(identifier)
def consume(self, p):
identifier = objc_str(self.identifier(p))
return self.helper.hasPurchasedConsumable_(identifier)
def is_deferred(self, p):
identifier = objc_str(self.identifier(p))
return self.helper.isDeferred_(identifier)
def get_price(self, p):
if renpy.predicting():
return None
identifier = objc_str(self.identifier(p))
rv = self.helper.formatPrice_(identifier)
if rv is not None:
rv = rv.UTF8String().decode("utf-8")
return rv
def init(self):
self.helper.validateProductIdentifiersInBackground()
# The backend we're using.
backend = NoneBackend()
# A map from product identifier to the product object.
products = { }
def register(product, identifier=None, amazon=None, google=None, ios=None, consumable=False):
"""
:doc: iap
Registers a product with the in-app purchase system.
`product`
A string giving the high-level name of the product. This is the
string that will be passed to :func:`iap.purchase`, :func:`iap.Purchase`,
and :func:`iap.has_purchased` to represent this product.
`identifier`
A string that's used to identify the product internally. Once used
to represent a product, this must never change. These strings are
generally of the form "com.domain.game.product".
If None, defaults to `product`.
`amazon`
A string that identifies the product in the Amazon app store.
If not given, defaults to `identifier`.
`google`
A string that identifies the product in the Google Play store.
If not given, defaults to `identifier`.
`ios`
A string that identifies the product in the Apple App store for
iOS. If not given, defaults to `identifier`.
`consumable`
True if this is a consumable purchase. Right now, consumable purchases
are only supported on iOS.
"""
if product in products:
raise Exception('Product %r has already been registered.' % product)
identifier = identifier or product
amazon = amazon or identifier
google = google or identifier
ios = ios or identifier
p = Product(product, identifier, google, amazon, ios, consumable)
products[product] = p
def with_background(f, *args, **kwargs):
"""
Displays the background, then invokes `f`.
"""
if background is not None:
renpy.scene()
renpy.show(background)
renpy.pause(0)
return f(*args, **kwargs)
def restore(interact=True):
"""
:doc: iap
Contacts the app store and restores any missing purchases.
`interact`
If True, renpy.pause will be called while waiting for the app store
to respond.
"""
backend.restore_purchases(interact)
for p in products.values():
persistent._iap_purchases[p.identifier] = backend.has_purchased(p)
class Restore(Action):
"""
:doc: iap_actions
An Action that contacts the app store and restores any missing purchases.
"""
def __call__(self):
renpy.invoke_in_new_context(with_background, restore)
def get_sensitive(self):
return get_store_name()
def get_product(product):
p = products.get(product, None)
if p is None:
raise Exception("Product %r is has not been registered.")
return p
def purchase(product, interact=True, consumable=False):
"""
:doc: iap
:args: (product, interact=True)
This function requests the purchase of `product`.
It returns true if the purchase succeeds, or false if the purchase
fails. If the product has been registered as consumable, the purchase
is consumed before this call returns.
"""
p = get_product(product)
# For compatibility with Winter Wolves' old code.
if consumable:
p.consumable = True
if not p.consumable:
if persistent._iap_purchases[p.identifier]:
return True
backend.purchase(p, interact)
if not p.consumable:
if backend.has_purchased(p):
persistent._iap_purchases[p.identifier] = True
return True
else:
return False
else:
return backend.consume(p)
class Purchase(Action):
"""
:doc: iap_actions
An action that attempts the purchase of `product`. This action is
sensitive if and only if the product is purchasable (a store is
enabled, and the product has not already been purchased.)
`success`
If not None, this is an action or list of actions that are run
when the purchase succeeds.
"""
def __init__(self, product, success=None):
self.product = product
self.sensitive = True
self.success = success
def __call__(self):
result = renpy.invoke_in_new_context(with_background, purchase, self.product)
if result:
renpy.run_action(self.success)
renpy.restart_interaction()
def should_be_sensitive(self):
if not get_store_name():
return False
if has_purchased(self.product):
return False
if is_deferred(self.product):
return False
return True
def get_sensitive(self):
self.sensitive = self.should_be_sensitive()
return self.sensitive
def periodic(self, st):
if self.should_be_sensitive() != self.sensitive:
renpy.restart_interaction()
return 5.0
def has_purchased(product):
"""
:doc: iap
Returns True if the user has purchased `product` in the past, and
False otherwise.
"""
p = get_product(product)
# Check the cache first, since we might be off line.
if persistent._iap_purchases.get(p.identifier, False):
return True
# Then ask the backend, in case we bought the product
# recently.
return backend.has_purchased(p)
def is_deferred(product):
"""
:doc: iap
Returns True if the user has asked to purchase `product`, but that
request has to be approved by a third party, such as a parent or
guardian.
"""
p = get_product(product)
# Then ask the backend, in case we bought the product
# recently.
return backend.is_deferred(p)
def get_price(product):
"""
:doc: iap
Returns a string giving the price of the `product` in the user's
local currency. Returns None if the price of the product is unknown -
which indicates the product cannot be purchased.
"""
p = get_product(product)
if p.price is None:
p.price = backend.get_price(p)
return p.price
def get_store_name():
"""
:doc: iap
Returns the name of the enabled store for in-app purchase. This
currently returns one of "amazon", "play" (for Google Play), "ios"
or None if no store is available.
"""
return backend.get_store_name()
def missing_products():
"""
Determines if any products are missing from persistent._iap_purchases
"""
for p in products.values():
if p.identifier not in persistent._iap_purchases:
return True
return False
def init_android():
"""
Initialize IAP on Android.
"""
from jnius import autoclass
Store = autoclass(b'org.renpy.iap.Store')
store = Store.getStore()
store_name = store.getStoreName()
if store_name == "none":
return NoneBackend()
return AndroidBackend(store, store_name)
def init():
"""
Called to initialize the IAP system.
"""
global backend
if persistent._iap_purchases is None:
persistent._iap_purchases = { }
# Do nothing if we have no products.
if not products:
return
for p in products.values():
if p.identifier not in persistent._iap_purchases:
persistent._iap_purchases[p.identifier] = False
# Set up the back end.
if renpy.renpy.android:
backend = init_android()
elif renpy.renpy.ios:
backend = IOSBackend()
else:
backend = NoneBackend()
# Restore purchases.
if products:
backend.init()
init 1500 python in iap:
init()