StillDeep SDK — Python client

Python client: grpcio-tools, code generation, shift, first sale.

Prerequisites

  • Python 3.11+
  • pip install grpcio grpcio-tools

Setup

mkdir my-pos-client && cd my-pos-client
python -m venv .venv && source .venv/bin/activate  # Windows: .venv\Scripts\activate
pip install grpcio grpcio-tools

cp -r ../waxpos/src/public/StillDeep.Intelligence.Contracts/protos ./protos
mkdir generated

python -m grpc_tools.protoc \
  -I./protos \
  --python_out=./generated \
  --grpc_python_out=./generated \
  ./protos/common.proto ./protos/receipt.proto ./protos/sales.proto ./protos/catalog.proto \
  ./protos/member.proto ./protos/device.proto ./protos/events.proto \
  ./protos/user.proto ./protos/shift.proto

Example: sign in, open the shift, sell one item

Sequence and required fields: see getting-started.md.

import os
import uuid
import grpc

import sys; sys.path.insert(0, './generated')
import common_pb2
import sales_pb2, sales_pb2_grpc
import shift_pb2, shift_pb2_grpc
import user_pb2, user_pb2_grpc

ADDRESS = os.environ.get('STILLDEEP_GRPC', 'localhost:50051')
POST_ID = os.environ['STILLDEEP_POST_ID']               # required: id of this post
SKU = os.environ['STILLDEEP_SKU']                       # product to sell
METHOD = os.environ.get('STILLDEEP_METHOD', 'cash')     # a code from ConfigService.GetPostConfig

def key() -> str:
    return str(uuid.uuid4())

with grpc.insecure_channel(ADDRESS) as channel:
    users = user_pb2_grpc.UserServiceStub(channel)
    shifts = shift_pb2_grpc.ShiftServiceStub(channel)
    sales = sales_pb2_grpc.SalesServiceStub(channel)

    # 1. Authenticate (required).
    auth = users.Authenticate(user_pb2.AuthenticateRequest(
        post_id=POST_ID, mode=user_pb2.AUTH_MODE_PASSWORD,
        login=os.environ['STILLDEEP_LOGIN'], password=os.environ['STILLDEEP_PASSWORD']))

    # 2-3. Reuse the open shift, or open one.
    try:
        shift = shifts.GetCurrentShift(shift_pb2.GetCurrentShiftRequest(post_id=POST_ID))
    except grpc.RpcError as e:
        if e.code() != grpc.StatusCode.NOT_FOUND:
            raise
        shift = shifts.OpenShift(shift_pb2.OpenShiftRequest(
            post_id=POST_ID, user_id=auth.user.user_id,
            opening_cash=common_pb2.Money(cents=0), idempotency_key=key())).shift

    # Every following call carries x-shift-id.
    md = [('x-shift-id', shift.shift_id)]

    # 4. CreateCart (required).
    cart_id = sales.CreateCart(sales_pb2.CreateCartRequest(
        post_id=POST_ID, idempotency_key=key()), metadata=md).cart.cart_id

    # 5. AddItem (required): product_id, sku or barcode; quantity in milli-units.
    sales.AddItem(sales_pb2.AddItemRequest(
        cart_id=cart_id, sku=SKU, quantity=common_pb2.Quantity(milli_units=1000),
        idempotency_key=key()), metadata=md)

    # 6. BeginPayment (optional): handle an extension question before paying.
    checkout = sales.BeginPayment(sales_pb2.BeginPaymentRequest(cart_id=cart_id), metadata=md)
    if checkout.HasField('question'):
        raise RuntimeError(f'Answer required: {checkout.question.code}')

    # 8. ProcessPayment (required): pay the remaining amount.
    paid = sales.ProcessPayment(sales_pb2.ProcessPaymentRequest(
        cart_id=cart_id, method_code=METHOD, amount=checkout.cart.remaining_due,
        idempotency_key=key()), metadata=md)
    if paid.payment.status in (sales_pb2.PAYMENT_STATUS_DECLINED, sales_pb2.PAYMENT_STATUS_FAILED):
        raise RuntimeError(paid.refusal_guide.user_facing_message)

    # 9. FinalizeTransaction (required).
    done = sales.FinalizeTransaction(sales_pb2.FinalizeRequest(
        cart_id=cart_id, idempotency_key=key()), metadata=md)
    print('Receipt', done.transaction.receipt_number, 'change', paid.change_due.cents)

Event stream

EventService is declared in events.proto but not served by the current POSIntelligenceSvc (UNIMPLEMENTED).

import events_pb2, events_pb2_grpc

with grpc.insecure_channel(ADDRESS) as channel:
    stub = events_pb2_grpc.EventServiceStub(channel)
    request = events_pb2.SubscribeRequest(subscriber_id=str(uuid.uuid4()), post_id_filter=POST_ID)
    for ev in stub.Subscribe(request, metadata=md):
        print(ev.WhichOneof('payload'), ev)