StillDeep SDK — Go client
Go client: protoc-gen-go-grpc, code generation, shift, first sale.
Prerequisites
- Go 1.23+
protoc25+- Plugins:
protoc-gen-go,protoc-gen-go-grpc
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
Setup
The .proto files declare no go_package: map each file to your Go package with M options.
mkdir my-pos-client && cd my-pos-client
go mod init example.com/my-pos-client
go get google.golang.org/grpc google.golang.org/protobuf
cp -r ../waxpos/src/public/StillDeep.Intelligence.Contracts/protos ./protos
mkdir -p generated
PKG=example.com/my-pos-client/generated
FILES="common receipt sales catalog member device events user shift"
OPTS=""
for f in $FILES; do OPTS="$OPTS --go_opt=M$f.proto=$PKG --go-grpc_opt=M$f.proto=$PKG"; done
protoc \
--proto_path=./protos \
--go_out=./generated \
--go_opt=paths=source_relative \
--go-grpc_out=./generated \
--go-grpc_opt=paths=source_relative \
$OPTS \
$(for f in $FILES; do echo ./protos/$f.proto; done)
Example: sign in, open the shift, sell one item
Sequence and required fields: see getting-started.md.
package main
import (
"context"
"crypto/rand"
"fmt"
"log"
"os"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
pb "example.com/my-pos-client/generated"
)
func key() string {
b := make([]byte, 16)
rand.Read(b)
b[6], b[8] = b[6]&0x0f|0x40, b[8]&0x3f|0x80
return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
}
func main() {
postID := os.Getenv("STILLDEEP_POST_ID") // required: id of this post
sku := os.Getenv("STILLDEEP_SKU") // product to sell
method := "cash" // a code from ConfigService.GetPostConfig
conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil { log.Fatal(err) }
defer conn.Close()
users := pb.NewUserServiceClient(conn)
shifts := pb.NewShiftServiceClient(conn)
sales := pb.NewSalesServiceClient(conn)
ctx := context.Background()
// 1. Authenticate (required).
auth, err := users.Authenticate(ctx, &pb.AuthenticateRequest{
PostId: postID, Mode: pb.AuthMode_AUTH_MODE_PASSWORD,
Login: os.Getenv("STILLDEEP_LOGIN"), Password: os.Getenv("STILLDEEP_PASSWORD"),
})
if err != nil { log.Fatal(err) }
// 2-3. Reuse the open shift, or open one.
shift, err := shifts.GetCurrentShift(ctx, &pb.GetCurrentShiftRequest{PostId: postID})
if status.Code(err) == codes.NotFound {
opened, openErr := shifts.OpenShift(ctx, &pb.OpenShiftRequest{
PostId: postID, UserId: auth.GetUser().GetUserId(),
OpeningCash: &pb.Money{Cents: 0}, IdempotencyKey: key(),
})
if openErr != nil { log.Fatal(openErr) }
shift, err = opened.GetShift(), nil
}
if err != nil { log.Fatal(err) }
// Every following call carries x-shift-id.
sctx := metadata.AppendToOutgoingContext(ctx, "x-shift-id", shift.GetShiftId())
// 4. CreateCart (required).
created, err := sales.CreateCart(sctx, &pb.CreateCartRequest{PostId: postID, IdempotencyKey: key()})
if err != nil { log.Fatal(err) }
cartID := created.GetCart().GetCartId()
// 5. AddItem (required): product_id, sku or barcode; quantity in milli-units.
if _, err = sales.AddItem(sctx, &pb.AddItemRequest{
CartId: cartID, Sku: sku, Quantity: &pb.Quantity{MilliUnits: 1000}, IdempotencyKey: key(),
}); err != nil { log.Fatal(err) }
// 6. BeginPayment (optional): handle an extension question before paying.
checkout, err := sales.BeginPayment(sctx, &pb.BeginPaymentRequest{CartId: cartID})
if err != nil { log.Fatal(err) }
if q := checkout.GetQuestion(); q != nil { log.Fatalf("answer required: %s", q.GetCode()) }
// 8. ProcessPayment (required): pay the remaining amount.
paid, err := sales.ProcessPayment(sctx, &pb.ProcessPaymentRequest{
CartId: cartID, MethodCode: method, Amount: checkout.GetCart().GetRemainingDue(), IdempotencyKey: key(),
})
if err != nil { log.Fatal(err) }
switch paid.GetPayment().GetStatus() {
case pb.PaymentStatus_PAYMENT_STATUS_DECLINED, pb.PaymentStatus_PAYMENT_STATUS_FAILED:
log.Fatal(paid.GetRefusalGuide().GetUserFacingMessage())
}
// 9. FinalizeTransaction (required).
done, err := sales.FinalizeTransaction(sctx, &pb.FinalizeRequest{CartId: cartID, IdempotencyKey: key()})
if err != nil { log.Fatal(err) }
log.Printf("receipt %s, change %d cents", done.GetTransaction().GetReceiptNumber(), paid.GetChangeDue().GetCents())
}