Skip to content

Commit

Permalink
Merge pull request #287 from planetarium/feature/update-product-schema
Browse files Browse the repository at this point in the history
Feature/update product schema
  • Loading branch information
U-lis authored Aug 1, 2024
2 parents 1218c8b + 986018f commit 6498481
Show file tree
Hide file tree
Showing 18 changed files with 139 additions and 52 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ on:
required: true
REFUND_SHEET_ID:
required: true
HEADLESS_GQL_JWT_SECRET:
required: true

jobs:
deployment:
Expand Down Expand Up @@ -153,6 +155,7 @@ jobs:
VOUCHER_JWT_SECRET: ${{ secrets.VOUCHER_JWT_SECRET }}
BRIDGE_DATA: ${{ secrets.BRIDGE_DATA }}
REFUND_SHEET_ID : ${{ secrets.REFUND_SHEET_ID }}
HEADLESS_GQL_JWT_SECRET: ${{ secrets.HEADLESS_GQL_JWT_SECRET }}
run: |
source $VENV
yarn cdk synth
Expand Down Expand Up @@ -189,6 +192,7 @@ jobs:
VOUCHER_JWT_SECRET: ${{ secrets.VOUCHER_JWT_SECRET }}
BRIDGE_DATA: ${{ secrets.BRIDGE_DATA }}
REFUND_SHEET_ID : ${{ secrets.REFUND_SHEET_ID }}
HEADLESS_GQL_JWT_SECRET: ${{ secrets.HEADLESS_GQL_JWT_SECRET }}
run: |
source $VENV
yarn cdk deploy --all --require-approval never -O output.txt
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ jobs:
APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
GOOGLE_CREDENTIAL: ${{ secrets.GOOGLE_CREDENTIAL }}
SEASON_PASS_JWT_SECRET: ${{ secrets.SEASON_PASS_JWT_SECRET }}
HEADLESS_GQL_JWT_SECRET: ${{ secrets.HEADLESS_GQL_JWT_SECRET }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

build_frontend:
Expand Down Expand Up @@ -56,6 +57,7 @@ jobs:
VOUCHER_JWT_SECRET: ${{ secrets.VOUCHER_JWT_SECRET }}
BRIDGE_DATA: ${{ secrets.BRIDGE_DATA }}
REFUND_SHEET_ID : ${{ secrets.REFUND_SHEET_ID }}
HEADLESS_GQL_JWT_SECRET: ${{ secrets.HEADLESS_GQL_JWT_SECRET }}

deploy_without_approval:
# This is for internal deployment
Expand Down Expand Up @@ -83,6 +85,7 @@ jobs:
VOUCHER_JWT_SECRET: ${{ secrets.VOUCHER_JWT_SECRET }}
BRIDGE_DATA: ${{ secrets.BRIDGE_DATA }}
REFUND_SHEET_ID : ${{ secrets.REFUND_SHEET_ID }}
HEADLESS_GQL_JWT_SECRET: ${{ secrets.HEADLESS_GQL_JWT_SECRET }}

approval:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -122,3 +125,4 @@ jobs:
VOUCHER_JWT_SECRET: ${{ secrets.VOUCHER_JWT_SECRET }}
BRIDGE_DATA: ${{ secrets.BRIDGE_DATA }}
REFUND_SHEET_ID : ${{ secrets.REFUND_SHEET_ID }}
HEADLESS_GQL_JWT_SECRET: ${{ secrets.HEADLESS_GQL_JWT_SECRET }}
3 changes: 3 additions & 0 deletions .github/workflows/synth.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ on:
required: true
REFUND_SHEET_ID:
required: true
HEADLESS_GQL_JWT_SECRET:
required: true

jobs:
synth:
Expand Down Expand Up @@ -140,6 +142,7 @@ jobs:
VOUCHER_JWT_SECRET: ${{ secrets.VOUCHER_JWT_SECRET }}
BRIDGE_DATA: ${{ secrets.BRIDGE_DATA }}
REFUND_SHEET_ID : ${{ secrets.REFUND_SHEET_ID }}
HEADLESS_GQL_JWT_SECRET: ${{ secrets.HEADLESS_GQL_JWT_SECRET }}
run: |
source $VENV
yarn cdk synth
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ on:
required: true
SEASON_PASS_JWT_SECRET:
required: true
HEADLESS_GQL_JWT_SECRET:
required: true
SLACK_WEBHOOK_URL:
required: true

Expand Down Expand Up @@ -87,6 +89,7 @@ jobs:
APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
APPLE_BUNDLE_ID: ${{ vars.APPLE_BUNDLE_ID }}
SEASON_PASS_JWT_SECRET: ${{ secrets.SEASON_PASS_JWT_SECRET }}
HEADLESS_GQL_JWT_SECRET: ${{ secrets.HEADLESS_GQL_JWT_SECRET }}
run: |
poetry run pytest tests
Expand Down
1 change: 1 addition & 0 deletions common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class Config:
cdn_host_k: str
odin_gql_url: str
heimdall_gql_url: str
headless_gql_jwt_secret: str

# Multiplanetary
planet_url: str
Expand Down
23 changes: 18 additions & 5 deletions common/_graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
from typing import Union, Dict, Any, Tuple, Optional

import jwt
from gql import Client
from gql.dsl import DSLSchema, dsl_gql, DSLQuery, DSLMutation
from gql.transport.requests import RequestsHTTPTransport
Expand All @@ -12,16 +13,28 @@


class GQL:
def __init__(self, url: str = f"{os.environ.get('HEADLESS')}/graphql"):
def __init__(self, url: str = f"{os.environ.get('HEADLESS')}/graphql", jwt_secret: str = None):
self._url = url
self.__jwt_secret = jwt_secret
self.client = None
self.ds = None
transport = RequestsHTTPTransport(url=self._url, verify=True, retries=2)
transport = RequestsHTTPTransport(url=self._url, verify=True, retries=2, headers=self.__create_header())
self.client = Client(transport=transport, fetch_schema_from_transport=True)
with self.client as _:
assert self.client.schema is not None
self.ds = DSLSchema(self.client.schema)

def create_token(self) -> str:
iat = datetime.datetime.utcnow()
return jwt.encode({
"iat": iat,
"exp": iat + datetime.timedelta(minutes=1),
"iss": "planetariumhq.com"
}, self.__jwt_secret)

def __create_header(self):
return {"Authorization": f"Bearer {self.create_token()}"}

def execute(self, query: DocumentNode) -> Union[Dict[str, Any], ExecutionResult]:
with self.client as sess:
return sess.execute(query)
Expand Down Expand Up @@ -52,7 +65,7 @@ def get_next_nonce(self, address: str) -> int:
return resp["transaction"]["nextTxNonce"]

def _unload_from_garage(self, pubkey: bytes, nonce: int, **kwargs) -> bytes:
ts = kwargs.get("timestamp", (datetime.datetime.utcnow()+datetime.timedelta(days=1)).isoformat())
ts = kwargs.get("timestamp", (datetime.datetime.utcnow() + datetime.timedelta(days=1)).isoformat())
fav_data = kwargs.get("fav_data")
avatar_addr = kwargs.get("avatar_addr")
item_data = kwargs.get("item_data")
Expand Down Expand Up @@ -81,7 +94,7 @@ def _unload_from_garage(self, pubkey: bytes, nonce: int, **kwargs) -> bytes:
return bytes.fromhex(result["actionTxQuery"]["unloadFromMyGarages"])

def _claim_items(self, pubkey: bytes, nonce: int, **kwargs) -> bytes:
ts = kwargs.get("timestamp", (datetime.datetime.utcnow()+datetime.timedelta(days=1)).isoformat())
ts = kwargs.get("timestamp", (datetime.datetime.utcnow() + datetime.timedelta(days=1)).isoformat())
claim_data = kwargs.get("claim_data")
memo = kwargs.get("memo")

Expand All @@ -103,7 +116,7 @@ def _claim_items(self, pubkey: bytes, nonce: int, **kwargs) -> bytes:
return bytes.fromhex(result["actionTxQuery"]["unloadFromMyGarages"])

def _transfer_asset(self, pubkey: bytes, nonce: int, **kwargs) -> bytes:
ts = kwargs.get("timestamp", (datetime.datetime.utcnow()+datetime.timedelta(days=1)).isoformat())
ts = kwargs.get("timestamp", (datetime.datetime.utcnow() + datetime.timedelta(days=1)).isoformat())
sender = kwargs.get("sender")
recipient = kwargs.get("recipient")
currency = kwargs.get("currency")
Expand Down
1 change: 1 addition & 0 deletions common/shared_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
("APPLE_CREDENTIAL", True),
("SEASON_PASS_JWT_SECRET", True),
("VOUCHER_JWT_SECRET", True),
("HEADLESS_GQL_JWT_SECRET", True)
)
ssm = boto3.client("ssm", region_name=config.region_name,
aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID"),
Expand Down
13 changes: 9 additions & 4 deletions iap/api/purchase.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ def request_product(receipt_data: ReceiptSchema,
# raise_error(sess, receipt, ValueError(
# f"Invalid Product ID: Given {product.google_sku} is not identical to found from receipt: {purchase.productId}"))
if success:
ack_google(product_id, token)
ack_google(x_iap_packagename, product_id, token)
## Apple
elif receipt_data.store in (Store.APPLE, Store.APPLE_TEST):
success, msg, purchase = validate_apple(receipt.package_name, order_id)
Expand Down Expand Up @@ -279,6 +279,7 @@ def request_product(receipt_data: ReceiptSchema,
"agent_addr": receipt.agent_addr,
"avatar_addr": receipt.avatar_addr,
"planet_id": receipt_data.planetId.decode(),
"package_name": receipt.package_name,
}))
logger.info(f"Voucher message: {resp['MessageId']}")

Expand Down Expand Up @@ -378,7 +379,11 @@ def free_product(receipt_data: FreeReceiptSchema,
.options(joinedload(Product.fav_list)).options(joinedload(Product.fungible_item_list))
.where(
Product.active.is_(True),
or_(Product.google_sku == receipt_data.sku, Product.apple_sku == receipt_data.sku)
or_(
Product.google_sku == receipt_data.sku,
Product.apple_sku == receipt_data.sku,
Product.apple_sku_k == receipt_data.sku
)
)
)
order_id = f"FREE-{uuid4()}"
Expand All @@ -400,8 +405,8 @@ def free_product(receipt_data: FreeReceiptSchema,
# Validation
if not product:
receipt.status = ReceiptStatus.INVALID
receipt.msg = f"Product {receipt_data.product_id} not exists or inactive"
raise_error(sess, receipt, ValueError(f"Product {receipt_data.product_id} not found or inactive"))
receipt.msg = f"Product {receipt_data.sku} not exists or inactive"
raise_error(sess, receipt, ValueError(f"Product {receipt_data.sku} not found or inactive"))

if not product.is_free:
receipt.status = ReceiptStatus.INVALID
Expand Down
1 change: 1 addition & 0 deletions iap/iap_cdk_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
"CDN_HOST_K": config.cdn_host_k,
"PLANET_URL": config.planet_url,
"BRIDGE_DATA": config.bridge_data,
"HEADLESS_GQL_JWT_SECRET": config.headless_gql_jwt_secret,
}

# Lambda Function
Expand Down
1 change: 1 addition & 0 deletions iap/schemas/product.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class SimpleProductSchema(BaseSchema):
order: int
google_sku: str = ""
apple_sku: str = ""
apple_sku_k: str = ""
is_free: bool
# product_type: ProductType
daily_limit: Optional[int] = None
Expand Down
11 changes: 11 additions & 0 deletions iap/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
google_credential = None
apple_credential = None
season_pass_jwt_secret = None
headless_gql_jwt_secret = None

if not stage:
logging.error("Config file not found")
Expand Down Expand Up @@ -51,6 +52,15 @@
except Exception as e:
season_pass_jwt_secret = os.environ.get("SEASON_PASS_JWT_SECRET", "")

try:
headless_gql_jwt_secret = fetch_parameter(
os.environ.get("REGION_NAME"),
f"{stage}_9c_IAP_HEADLESS_GQL_JWT_SECRET",
True
)["Value"]
except Exception as e:
headless_gql_jwt_secret = os.environ.get("HEADLESS_GQL_JWT_SECRET", "")

# Prepare settings
DEBUG = config("DEBUG", cast=bool, default=False)
LOGGING_LEVEL = logging.getLevelName(config("LOGGING_LEVEL", default="INFO"))
Expand All @@ -71,3 +81,4 @@
REGION_NAME = config("REGION_NAME")

SEASON_PASS_JWT_SECRET = season_pass_jwt_secret or config("SEASON_PASS_JWT_SECRET")
HEADLESS_JWT_GQL_SECRET = headless_gql_jwt_secret or config("HEADLESS_GQL_JWT_SECRET")
19 changes: 4 additions & 15 deletions iap/validator/google.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,18 @@
from typing import Tuple, Optional

from common import logger
from common.enums import GooglePurchaseState
from common.enums import GooglePurchaseState, PackageName
from common.utils.google import get_google_client
from iap import settings
from iap.schemas.receipt import GooglePurchaseSchema


def ack_google(sku: str, token: str):
def ack_google(package_name: PackageName, sku: str, token: str):
client = get_google_client(settings.GOOGLE_CREDENTIAL)
try:
(client.purchases().products()
.acknowledge(packageName=settings.GOOGLE_PACKAGE_NAME, productId=sku, token=token)
.execute()
)
except Exception as e:
logger.error(e)

def consume_google(sku: str, token: str):
client = get_google_client(settings.GOOGLE_CREDENTIAL)
try:
(client.purchases().products()
.consume(packageName=settings.GOOGLE_PACKAGE_NAME, productId=sku, token=token)
.execute()
)
.acknowledge(packageName=package_name.value, productId=sku, token=token)
.execute())
except Exception as e:
logger.error(e)

Expand Down
14 changes: 14 additions & 0 deletions tests/common/test_graphql.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import pytest

from common._graphql import GQL
from iap import settings


@pytest.mark.parametrize("headless", [
"https://9c-main-jwt.nine-chronicles.com/graphql",
"https://heimdall-jwt.nine-chronicles.com/graphql",
])
def test_gql_jwt(headless):
gql = GQL(headless, settings.HEADLESS_JWT_GQL_SECRET)
test_nonce = gql.get_next_nonce("0x0000000000000000000000000000000000000000")
assert test_nonce == 0
54 changes: 34 additions & 20 deletions worker/worker/google_refund_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,20 @@
from typing import Optional

from common import logger
from common.enums import PackageName
from common.utils.aws import fetch_parameter
from common.utils.google import get_google_client, Spreadsheet

GOOGLE_PACKAGE_NAME = os.environ.get("GOOGLE_PACKAGE_NAME")
GOOGLE_PACKAGE_DICT = {
PackageName.NINE_CHRONICLES_M: {
"app_name": "9c M",
"sheet_name": "Google",
},
PackageName.NINE_CHRONICLES_K: {
"app_name": "9c K",
"sheet_name": "Google_K"
},
}
GOOGLE_CREDENTIAL = fetch_parameter(
os.environ.get("REGION_NAME"),
f"{os.environ.get('STAGE')}_9c_IAP_GOOGLE_CREDENTIAL", True
Expand Down Expand Up @@ -56,25 +66,29 @@ def __post_init__(self):
def handle(event, context):
client = get_google_client(GOOGLE_CREDENTIAL)
sheet = Spreadsheet(GOOGLE_CREDENTIAL, SHEET_ID)
prev_data = sheet.get_values("Google!A2:B").get("values", [])
prev_order_id = set([x[1] for x in prev_data])
last_num = int(prev_data[-1][0]) + 1 if prev_data else 1
logger.info(f"{len(prev_data)} refunded data are present.")
voided_list = client.purchases().voidedpurchases().list(packageName=GOOGLE_PACKAGE_NAME).execute()
voided_list = sorted([RefundData(**x) for x in voided_list["voidedPurchases"]], key=lambda x: x.voidedTimeMillis)

new_data = []
index = last_num
for void in voided_list:
if void.orderId in prev_order_id:
continue
new_data.append(
[index, void.orderId, void.purchaseTime.isoformat(), void.voidedTime.isoformat(), void.voidedSource.name,
void.voidedReason.name])
index += 1

sheet.set_values(f"Google!A{last_num + 1}:F", new_data)
logger.info(f"{len(new_data)} Refunds are added")

for package_name, data in GOOGLE_PACKAGE_DICT.items():
prev_data = sheet.get_values(f"{data['sheet_name']}!A2:B").get("values", [])
prev_order_id = set([x[1] for x in prev_data])
last_num = int(prev_data[-1][0]) + 1 if prev_data else 1
logger.info(f"{len(prev_data)} refunded data are present in {data['app_name']}")
voided_list = client.purchases().voidedpurchases().list(packageName=package_name.value).execute()
voided_list = sorted([RefundData(**x) for x in voided_list["voidedPurchases"]],
key=lambda x: x.voidedTimeMillis)

new_data = []
index = last_num
for void in voided_list:
if void.orderId in prev_order_id:
continue
new_data.append(
[index, void.orderId, void.purchaseTime.isoformat(), void.voidedTime.isoformat(),
void.voidedSource.name,
void.voidedReason.name])
index += 1

sheet.set_values(f"{data['sheet_name']}!A{last_num + 1}:F", new_data)
logger.info(f"{len(new_data)} Refunds are added to {data['app_name']}")


if __name__ == "__main__":
Expand Down
Loading

0 comments on commit 6498481

Please sign in to comment.