Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions api/handlers/subscription_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,12 @@ func CreateStripePaymentIntent(w http.ResponseWriter, r *http.Request) {
func StripeCreateCustomerPortal(w http.ResponseWriter, r *http.Request) {
router.WrapWithInputRequireAuth(controller.StripeCreateCustomerPortal, w, r)
}

func AppSumoPurchaseWebhook(w http.ResponseWriter, r *http.Request) {
router.WrapWithInputBodyFormatterNoAuth(
controller.VerifyAppSumoBody,
controller.AppSumoWebhook,
w,
r,
)
}
1 change: 1 addition & 0 deletions api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ Options:
router.NewRoute("POST", "/solana/payment-intent", handlers.CreateSolanaPaymentIntent),
router.NewRoute("POST", "/stripe/payment-intent", handlers.CreateStripePaymentIntent),
router.NewRoute("POST", "/stripe/customer-portal", handlers.StripeCreateCustomerPortal),
router.NewRoute("POST", "/app-sumo/purchase", handlers.AppSumoPurchaseWebhook),
router.NewRoute("GET", "/wallet/balance", handlers.WalletBalance),
router.NewRoute("POST", "/wallet/validate-address", handlers.WalletValidateAddress),
router.NewRoute("POST", "/wallet/circle-init", handlers.WalletCircleInit),
Expand Down
32 changes: 32 additions & 0 deletions controller/subscription_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2228,3 +2228,35 @@ func CreateSolanaPaymentIntent(
model.CreateSolanaPaymentIntent(intent.Reference, clientSession)
return &SolanaPaymentIntentResult{}, nil
}

/**
* App Sumo Webhook
*/

func VerifyAppSumoBody(req *http.Request) (io.Reader, error) {
bodyBytes, err := io.ReadAll(req.Body)
if err != nil {
return nil, err
}

// todo - authenticate the request properly

return bytes.NewReader(bodyBytes), nil
}

type AppSumoWebhookArgs struct{}

type AppSumoWebhookResult struct{}

func AppSumoWebhook(
appSumoWebhook *AppSumoWebhookArgs,
clientSession *session.ClientSession,
) (*AppSumoWebhookResult, error) {

_, err := model.CreateAccountRedemptionCode(clientSession.Ctx)
if err != nil {
return nil, err
}

return &AppSumoWebhookResult{}, nil
}
9 changes: 9 additions & 0 deletions db_migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -2664,4 +2664,13 @@ var migrations = []any{
DROP CONSTRAINT client_connection_reliability_score_pkey,
ADD PRIMARY KEY (client_id, lookback_index)
`),

newSqlMigration(`
CREATE TABLE account_redemption_code (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
code varchar(64) NOT NULL UNIQUE,
network_id uuid,
create_time timestamp NOT NULL DEFAULT now()
)
`),
}
80 changes: 80 additions & 0 deletions model/account_redemption_code_model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package model

import (
"context"

"github.com/urnetwork/glog"
"github.com/urnetwork/server"
"github.com/urnetwork/server/session"
)

/**
* Account redemption codes allow users to redeem special offers or credits to their accounts.
* Currently, created in the AppSumo webhook
*/

func CreateAccountRedemptionCode(ctx context.Context) (redemptionCode string, returnErr error) {

server.Tx(ctx, func(tx server.PgTx) {

redemptionCode = generateAlphanumericCode(8)

_, err := tx.Exec(
ctx,
`
INSERT INTO account_redemption_code (
code
)
VALUES ($1)
`,
redemptionCode,
)

if err != nil {
glog.Infof("Error creating account redemption code: %v", err)
returnErr = err
}

})

return

}

func ClaimAccountRedemptionCode(
code string,
session *session.ClientSession,
) (claimed bool, err error) {

server.Tx(session.Ctx, func(tx server.PgTx) {

result, execErr := tx.Exec(
session.Ctx,
`
UPDATE account_redemption_code
SET network_id = $2
WHERE code = $1 AND network_id IS NULL
`,
code,
session.ByJwt.NetworkId,
)

if execErr != nil {
err = execErr
return
}

// check if a row was updated
if result.RowsAffected() == 1 {
claimed = true
} else {
claimed = false // already claimed or code doesn't exist
}

// todo - apply credits or bonus here

})

return

}
52 changes: 52 additions & 0 deletions model/account_redemption_code_model_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package model

import (
"context"
"testing"

"github.com/go-playground/assert/v2"
"github.com/urnetwork/server"
"github.com/urnetwork/server/jwt"
"github.com/urnetwork/server/session"
)

func TestAccountRedemptionCode(t *testing.T) {
server.DefaultTestEnv().Run(func() {

ctx := context.Background()
networkId := server.NewId()
clientId := server.NewId()

sessionA := session.Testing_CreateClientSession(ctx, &jwt.ByJwt{
NetworkId: networkId,
ClientId: &clientId,
})

redemptionCode, err := CreateAccountRedemptionCode(ctx)
assert.Equal(t, err, nil)
assert.Equal(t, len(redemptionCode), 8)

claimed, err := ClaimAccountRedemptionCode(redemptionCode, sessionA)
assert.Equal(t, err, nil)
assert.Equal(t, claimed, true)

// Try and claim again, should fail
claimed, err = ClaimAccountRedemptionCode(redemptionCode, sessionA)
assert.Equal(t, err, nil)
assert.Equal(t, claimed, false)

// different account trying to claim, should fail
networkIdB := server.NewId()
clientIdB := server.NewId()

sessionB := session.Testing_CreateClientSession(ctx, &jwt.ByJwt{
NetworkId: networkIdB,
ClientId: &clientIdB,
})

claimed, err = ClaimAccountRedemptionCode(redemptionCode, sessionB)
assert.Equal(t, err, nil)
assert.Equal(t, claimed, false)

})
}