diff --git a/.github/.example/go.mod b/.github/.example/go.mod new file mode 100644 index 0000000..e63216c --- /dev/null +++ b/.github/.example/go.mod @@ -0,0 +1,14 @@ +module example + +go 1.23.3 + +replace github.com/talon-one/talon_go/v8 => ../.. + +require github.com/talon-one/talon_go/v8 v8.0.0 + +require ( + github.com/golang/protobuf v1.2.0 // indirect + golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e // indirect + golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 // indirect + google.golang.org/appengine v1.4.0 // indirect +) diff --git a/.github/.example/go.sum b/.github/.example/go.sum new file mode 100644 index 0000000..734252e --- /dev/null +++ b/.github/.example/go.sum @@ -0,0 +1,13 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/.github/.example/main.go b/.github/.example/main.go new file mode 100644 index 0000000..b926f01 --- /dev/null +++ b/.github/.example/main.go @@ -0,0 +1,123 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "os" + + talon "github.com/talon-one/talon_go/v8" +) + +func main() { + configuration := talon.NewConfiguration() + // Set API base path + configuration.Servers = talon.ServerConfigurations{ + { + // Notice that there is no trailing '/' + URL: "http://localhost:9000", + Description: "Talon.One's API base URL", + }, + } + // If you wish to inject a custom implementation of HTTPClient + // configuration.HTTPClient = &customHTTPClient + + integrationClient := talon.NewAPIClient(configuration) + + // Create integration authentication context using api key + integrationAuthContext := context.WithValue(context.Background(), talon.ContextAPIKeys, map[string]talon.APIKey{ + "Authorization": { + Prefix: "ApiKey-v1", + Key: os.Getenv("TALON_API_KEY"), + }, + }) + + // Instantiating a NewCustomerSessionV2 struct + newCustomerSession := talon.NewCustomerSessionV2{ + // You can use either struct literals + ProfileId: talon.PtrString("DEADBEEF"), + CouponCodes: &[]string{"Cool-Stuff!"}, + } + + // Or alternatively, using the relevant setter in a later stage in the code + newCustomerSession.SetCartItems([]talon.CartItem{ + { + Name: talon.PtrString("Pad Thai - Veggie"), + Sku: "pad-332", + Quantity: 1, + Price: talon.PtrFloat32(5.5), + Category: talon.PtrString("Noodles"), + }, + { + Name: talon.PtrString("Chang"), + Sku: "chang-br-42", + Quantity: 1, + Price: talon.PtrFloat32(2.3), + Category: talon.PtrString("Beverages"), + }, + }) + + // Instantiating a new IntegrationRequest + integrationRequest := talon.IntegrationRequest{ + CustomerSession: newCustomerSession, + } + + // Optional list of requested information to be present on the response. + // See docs/IntegrationRequest.md for full list of supported values + // integrationRequest.SetResponseContent([]string{ + // "customerSession", + // "customerProfile", + // "loyalty", + // }) + + // Create/update a customer session using `UpdateCustomerSessionV2` function + integrationState, _, err := integrationClient.IntegrationApi. + UpdateCustomerSessionV2(integrationAuthContext, "deetdoot_2"). + Body(integrationRequest). + Execute() + + if err != nil { + fmt.Printf("ERROR while calling UpdateCustomerSessionV2: %s\n", err) + return + } + fmt.Printf("%#v\n", integrationState) + + // Parsing the returned effects list, please consult https://developers.talon.one/Integration-API/handling-effects-v2 for the full list of effects and their corresponding properties + for _, effect := range integrationState.GetEffects() { + effectType := effect.GetEffectType() + switch { + case "setDiscount" == effectType: + // Initiating right props instance according to the effect type + effectProps := talon.SetDiscountEffectProps{} + if err := decodeHelper(effect.GetProps(), &effectProps); err != nil { + fmt.Printf("ERROR while decoding 'setDiscount' props: %s\n", err) + continue + } + + // Access the specific effect's properties + fmt.Printf("Set a discount '%s' of %2.3f\n", effectProps.GetName(), effectProps.GetValue()) + case "acceptCoupon" == effectType: + // Initiating right props instance according to the effect type + effectProps := talon.AcceptCouponEffectProps{} + if err := decodeHelper(effect.GetProps(), &effectProps); err != nil { + fmt.Printf("ERROR while decoding props: %s\n", err) + continue + } + + // Work with AcceptCouponEffectProps' properties + // ... + default: + fmt.Printf("Encounter unknown effect type: %s\n", effectType) + } + } +} + +// quick decoding of props-map into our library structures using JSON marshaling, +// or alternatively using a library like https://github.com/mitchellh/mapstructure +func decodeHelper(propsMap map[string]interface{}, v interface{}) error { + propsJSON, err := json.Marshal(propsMap) + if err != nil { + return err + } + return json.Unmarshal(propsJSON, v) +} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..863736a --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,76 @@ +name: run tests + +on: [push] + +jobs: + test: + runs-on: ubuntu-latest + permissions: + contents: 'read' + id-token: 'write' + + steps: + - uses: actions/checkout@v4 + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@v1 + with: + token_format: access_token + workload_identity_provider: projects/949875736540/locations/global/workloadIdentityPools/external-pool/providers/github-provider + service_account: artifact-pusher@talon-artifacts.iam.gserviceaccount.com + - name: Login to GAR + uses: docker/login-action@v3 + with: + registry: europe-west3-docker.pkg.dev + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + - uses: hoverkraft-tech/compose-action@v2.0.2 + - name: Set up Golang + uses: actions/setup-go@v5 + with: + go-version: 1.23.3 + - name: Install dependencies + run: | + sudo apt-get install jq curl + - name: Run example + working-directory: .github/.example + run: | + curl --location "http://localhost:9000/v1/users"; + sleep 5; + docker compose logs; + + ACCOUNT_RESPONSE=$(curl -s --location "http://localhost:9000/v1/accounts" \ + --header "Content-Type: application/json" \ + --data-raw '{ + "companyName": "demo", + "email": "integrationtest@talon.one", + "password": "Password1234!" + }'); + export TALON_USER_ID=$(echo $ACCOUNT_RESPONSE | jq ".userId"); + export TALON_USER_TOKEN=$(echo $ACCOUNT_RESPONSE | jq ".token" | tr -d '"'); + USER_RESPONSE=$(curl -s --location "http://localhost:9000/v1/users/$TALON_USER_ID" \ + --header "Authorization: Bearer $TALON_USER_TOKEN"); + export TALON_ACCOUNT_ID=$(echo $USER_RESPONSE | jq ".accountId"); + echo "User with ID $TALON_USER_ID and Token $TALON_USER_TOKEN was created for application $TALON_ACCOUNT_ID"; + APPLICATION_RESPONSE=$(curl -s --location "http://localhost:9000/v1/applications" \ + --header "Content-Type: application/json" \ + --header "Authorization: Bearer $TALON_USER_TOKEN" \ + --data-raw '{ + "name": "demo", + "currency": "EUR", + "timezone": "Europe/Berlin", + "enableFlattenedCartItems": false + }'); + export TALON_APPLICATION_ID=$(echo $USER_RESPONSE | jq ".id"); + echo "Application with ID $TALON_APPLICATION_ID was created" + API_KEY_RESPONSE=$(curl -s -v --location "http://localhost:9000/v1/applications/$TALON_APPLICATION_ID/apikeys" \ + --header "Content-Type: application/json" \ + --header "Authorization: Bearer $TALON_USER_TOKEN" \ + --data-raw '{ + "title": "Application HIT KEY", + "expires": "2099-01-01T0:00:00Z" + }'); + echo "Api-Key-Response: $API_KEY_RESPONSE"; + export TALON_API_KEY=$(echo $API_KEY_RESPONSE | jq ".key" | tr -d '"'); + echo "Api-Key $TALON_API_KEY created" + go run main.go; diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5b65193 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,48 @@ +services: + api-service: + image: europe-west3-docker.pkg.dev/talon-artifacts/talon-images/talon-service:master + depends_on: + - database-service + ports: + - "9000:9000" + environment: + - TALON_DB_NAME=talon + - TALON_DB_USER=talon + - TALON_DB_PASSWORD=talon.one.9000 + - TALON_DB_HOST=database-service + - TALON_DB_PORT=5432 + - TALON_ENABLE_WEBHOOK_WORKER_POOL=false + - TZ=UTC + - RELEASE_STAGE=ci + - TALON_CH_ENABLED=false + - TALON_DISABLE_PROFILER=true + - USE_REPLICA_DB=false + command: + - /talon/talon + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/v1/status/health"] + interval: 10s + timeout: 5s + retries: 10 + restart: "on-failure:10" + + database-service: + image: docker.io/bitnami/postgresql:15 + volumes: + - 'postgresql_master_data:/bitnami/postgresql' + ports: + - "5433:5432" + environment: + - POSTGRESQL_DATABASE=talon + - POSTGRESQL_USERNAME=talon + - POSTGRESQL_PASSWORD=talon.one.9000 + healthcheck: + test: ["CMD-SHELL", "pg_isready -U talon -d talon"] + interval: 10s + timeout: 5s + retries: 5 + restart: "on-failure:10" + +volumes: + postgresql_master_data: + \ No newline at end of file