Skip to content

Commit

Permalink
Update get purchase/subscription signature (#990)
Browse files Browse the repository at this point in the history
Set deleted user purchases/subs as belonging to system user.
Return empty user id when retrieving purchases/subscriptions belonging to the system user.
Do not return error when purchases validation contain mismatching userIDs.
  • Loading branch information
sesposito authored Feb 27, 2023
1 parent 5b4416e commit c4ad4ac
Show file tree
Hide file tree
Showing 13 changed files with 198 additions and 123 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr

### Changed
- Improve graceful shutdown of Google IAP receipt processor.
- If In-App Purchases validation contain mismatching userIDs, do not return an error.

### Fixed
- Consistent validation of override operator in runtime leaderboard record writes.
Expand Down
4 changes: 1 addition & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require (
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.5.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3
github.com/heroiclabs/nakama-common v1.26.0
github.com/heroiclabs/nakama-common v1.26.1-0.20230227112928-be92be890386
github.com/jackc/pgconn v1.13.0
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa
github.com/jackc/pgtype v1.12.0
Expand Down Expand Up @@ -75,5 +75,3 @@ require (
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d // indirect
golang.org/x/text v0.3.7 // indirect
)

replace github.com/heroiclabs/nakama-common => ../nakama-common
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/heroiclabs/nakama-common v1.26.0 h1:YcZwKjmxT6YvEhKuZ/bPnt0BV6PbrfapFis1ZiFzOOk=
github.com/heroiclabs/nakama-common v1.26.0/go.mod h1:zdYggBBPmykSfz4zYFJmBDX5wyURSPAGANtJPEDdbx8=
github.com/heroiclabs/nakama-common v1.26.1-0.20230227112928-be92be890386 h1:fCHcRLOFZDHy3KM+aZQUb/h/8uCfJJSzDKSCkwIRFvs=
github.com/heroiclabs/nakama-common v1.26.1-0.20230227112928-be92be890386/go.mod h1:zdYggBBPmykSfz4zYFJmBDX5wyURSPAGANtJPEDdbx8=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
Expand Down
67 changes: 67 additions & 0 deletions migrate/sql/20230222172540-purchases-not-null-uid.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2021 The Nakama Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

-- +migrate Up
ALTER TABLE purchase
DROP CONSTRAINT purchase_user_id_fkey;
ALTER TABLE subscription
DROP CONSTRAINT subscription_user_id_fkey;

UPDATE purchase
SET user_id = '00000000-0000-0000-0000-000000000000' WHERE user_id IS NULL;
UPDATE subscription
SET user_id = '00000000-0000-0000-0000-000000000000' WHERE user_id IS NULL;

ALTER TABLE purchase
ALTER COLUMN user_id SET NOT NULL;
ALTER TABLE purchase
ALTER COLUMN user_id SET DEFAULT '00000000-0000-0000-0000-000000000000';

ALTER TABLE subscription
ALTER COLUMN user_id SET NOT NULL;
ALTER TABLE subscription
ALTER COLUMN user_id SET DEFAULT '00000000-0000-0000-0000-000000000000';

ALTER TABLE purchase
ADD CONSTRAINT purchase_user_id_fkey FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET DEFAULT;
ALTER TABLE subscription
ADD CONSTRAINT subscription_user_id_fkey FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET DEFAULT;

-- +migrate Down
ALTER TABLE purchase
DROP CONSTRAINT purchase_user_id_fkey;
ALTER TABLE subscription
DROP CONSTRAINT subscription_user_id_fkey;

ALTER TABLE purchase
ALTER COLUMN user_id DROP DEFAULT;
ALTER TABLE purchase
ALTER COLUMN user_id DROP NOT NULL;

ALTER TABLE subscription
ALTER COLUMN user_id DROP DEFAULT;
ALTER TABLE subscription
ALTER COLUMN user_id DROP NOT NULL;

UPDATE purchase
SET user_id = NULL WHERE user_id = '00000000-0000-0000-0000-000000000000';
UPDATE subscription
SET user_id = NULL WHERE user_id = '00000000-0000-0000-0000-000000000000';

ALTER TABLE purchase
ADD CONSTRAINT purchase_user_id_fkey FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL;
ALTER TABLE subscription
ADD CONSTRAINT subscription_user_id_fkey FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL;
2 changes: 1 addition & 1 deletion server/api_subscription.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ func (s *ApiServer) GetSubscription(ctx context.Context, in *api.GetSubscription
}
}

_, sub, err := GetSubscriptionByProductId(ctx, s.logger, s.db, userID.String(), in.ProductId)
sub, err := GetSubscriptionByProductId(ctx, s.logger, s.db, userID.String(), in.ProductId)
if err != nil {
if err == ErrSubscriptionNotFound {
return nil, status.Error(codes.NotFound, err.Error())
Expand Down
101 changes: 36 additions & 65 deletions server/core_purchase.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,12 @@ func ValidatePurchasesApple(ctx context.Context, logger *zap.Logger, db *sql.DB,

validatedPurchases := make([]*api.ValidatedPurchase, 0, len(purchases))
for _, p := range purchases {
suid := p.userID.String()
if p.userID.IsNil() {
suid = ""
}
validatedPurchases = append(validatedPurchases, &api.ValidatedPurchase{
UserId: p.userID.String(),
UserId: suid,
ProductId: p.productId,
TransactionId: p.transactionId,
Store: p.store,
Expand Down Expand Up @@ -195,8 +199,12 @@ func ValidatePurchaseGoogle(ctx context.Context, logger *zap.Logger, db *sql.DB,

validatedPurchases := make([]*api.ValidatedPurchase, 0, len(purchases))
for _, p := range purchases {
suid := p.userID.String()
if p.userID.IsNil() {
suid = ""
}
validatedPurchases = append(validatedPurchases, &api.ValidatedPurchase{
UserId: userID.String(),
UserId: suid,
ProductId: p.productId,
TransactionId: p.transactionId,
Store: p.store,
Expand Down Expand Up @@ -273,8 +281,12 @@ func ValidatePurchaseHuawei(ctx context.Context, logger *zap.Logger, db *sql.DB,

validatedPurchases := make([]*api.ValidatedPurchase, 0, len(purchases))
for _, p := range purchases {
suid := p.userID.String()
if p.userID.IsNil() {
suid = ""
}
validatedPurchases = append(validatedPurchases, &api.ValidatedPurchase{
UserId: userID.String(),
UserId: suid,
ProductId: p.productId,
TransactionId: p.transactionId,
Store: p.store,
Expand All @@ -292,59 +304,7 @@ func ValidatePurchaseHuawei(ctx context.Context, logger *zap.Logger, db *sql.DB,
}, nil
}

func GetPurchaseByTransactionID(ctx context.Context, logger *zap.Logger, db *sql.DB, transactionID string) (string, *api.ValidatedPurchase, error) {
query := `
SELECT
user_id,
transaction_id,
product_id,
store,
raw_response,
purchase_time,
create_time,
update_time,
environment
FROM
purchase
WHERE
transaction_id = $1
`
var userID uuid.UUID
var transactionId string
var productId string
var store api.StoreProvider
var rawResponse string
var purchaseTime pgtype.Timestamptz
var createTime pgtype.Timestamptz
var updateTime pgtype.Timestamptz
var environment api.StoreEnvironment

if err := db.QueryRowContext(ctx, query, transactionID).Scan(&userID, &transactionId, &productId, &store, &rawResponse, &purchaseTime, &createTime, &updateTime, &environment); err != nil {
logger.Error("Error retrieving purchase.", zap.Error(err))
return "", nil, err
}

return userID.String(), &api.ValidatedPurchase{
UserId: userID.String(),
ProductId: productId,
TransactionId: transactionID,
Store: store,
ProviderResponse: rawResponse,
PurchaseTime: timestamppb.New(purchaseTime.Time),
CreateTime: timestamppb.New(createTime.Time),
UpdateTime: timestamppb.New(updateTime.Time),
Environment: environment,
}, nil
}

type purchasesListCursor struct {
TransactionId string
PurchaseTime *timestamppb.Timestamp
UserId string
IsNext bool
}

func getPurchaseByTransactionId(ctx context.Context, db *sql.DB, transactionId string) (*api.ValidatedPurchase, error) {
func GetPurchaseByTransactionId(ctx context.Context, db *sql.DB, transactionID string) (*api.ValidatedPurchase, error) {
var (
dbTransactionId string
dbUserId uuid.UUID
Expand Down Expand Up @@ -372,13 +332,18 @@ func getPurchaseByTransactionId(ctx context.Context, db *sql.DB, transactionId s
raw_response
FROM purchase
WHERE transaction_id = $1
`, transactionId).Scan(&dbUserId, &dbStore, &dbTransactionId, &dbCreateTime, &dbUpdateTime, &dbPurchaseTime, &dbRefundTime, &dbProductId, &dbEnvironment, &dbRawResponse)
`, transactionID).Scan(&dbUserId, &dbStore, &dbTransactionId, &dbCreateTime, &dbUpdateTime, &dbPurchaseTime, &dbRefundTime, &dbProductId, &dbEnvironment, &dbRawResponse)
if err != nil {
return nil, err
}

suid := dbUserId.String()
if dbUserId.IsNil() {
suid = ""
}

return &api.ValidatedPurchase{
UserId: dbUserId.String(),
UserId: suid,
ProductId: dbProductId,
TransactionId: dbTransactionId,
Store: dbStore,
Expand All @@ -391,6 +356,13 @@ func getPurchaseByTransactionId(ctx context.Context, db *sql.DB, transactionId s
}, nil
}

type purchasesListCursor struct {
TransactionId string
PurchaseTime *timestamppb.Timestamp
UserId string
IsNext bool
}

func ListPurchases(ctx context.Context, logger *zap.Logger, db *sql.DB, userID string, limit int, cursor string) (*api.PurchaseList, error) {
var incomingCursor *purchasesListCursor
if cursor != "" {
Expand Down Expand Up @@ -492,8 +464,13 @@ func ListPurchases(ctx context.Context, logger *zap.Logger, db *sql.DB, userID s
break
}

suid := dbUserID.String()
if dbUserID.IsNil() {
suid = ""
}

purchase := &api.ValidatedPurchase{
UserId: dbUserID.String(),
UserId: suid,
ProductId: productId,
TransactionId: transactionId,
Store: store,
Expand Down Expand Up @@ -581,8 +558,6 @@ func upsertPurchases(ctx context.Context, db *sql.DB, purchases []*storagePurcha
return nil, errors.New("expects at least one receipt")
}

userIDIn := purchases[0].userID

statements := make([]string, 0, len(purchases))
params := make([]interface{}, 0, len(purchases)*8)
transactionIDsToPurchase := make(map[string]*storagePurchase)
Expand Down Expand Up @@ -660,10 +635,6 @@ RETURNING

storedPurchases := make([]*storagePurchase, 0, len(transactionIDsToPurchase))
for _, purchase := range transactionIDsToPurchase {
if purchase.seenBefore && purchase.userID != userIDIn {
// Mismatch between userID requesting validation and existing receipt userID, return error.
return nil, status.Error(codes.FailedPrecondition, "Invalid receipt for userID.")
}
storedPurchases = append(storedPurchases, purchase)
}

Expand Down
Loading

0 comments on commit c4ad4ac

Please sign in to comment.