diff --git a/flows/inputs/msg.go b/flows/inputs/msg.go index 471a42f7b..f81ffd9b5 100644 --- a/flows/inputs/msg.go +++ b/flows/inputs/msg.go @@ -29,7 +29,7 @@ type MsgInput struct { text string attachments []utils.Attachment externalID string - order *types.XObject + order *flows.Order } // NewMsg creates a new user input based on a message @@ -85,7 +85,7 @@ func (i *MsgInput) Context(env envs.Environment) map[string]types.XValue { "text": types.NewXText(i.text), "attachments": types.NewXArray(attachments...), "external_id": types.NewXText(i.externalID), - "order": i.order, + "order": flows.Context(env, i.order), } } @@ -112,7 +112,7 @@ type msgInputEnvelope struct { Text string `json:"text"` Attachments []utils.Attachment `json:"attachments,omitempty"` ExternalID string `json:"external_id,omitempty"` - Order *types.XObject `json:"order,omitempty"` + Order *flows.Order `json:"order,omitempty"` } func readMsgInput(sessionAssets flows.SessionAssets, data json.RawMessage, missing assets.MissingCallback) (flows.Input, error) { diff --git a/flows/msg.go b/flows/msg.go index 8054c6b65..b0f0d0a9c 100644 --- a/flows/msg.go +++ b/flows/msg.go @@ -7,7 +7,6 @@ import ( "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/envs" - "github.com/nyaruka/goflow/excellent/types" "github.com/nyaruka/goflow/utils" validator "gopkg.in/go-playground/validator.v9" @@ -45,8 +44,8 @@ type BaseMsg struct { type MsgIn struct { BaseMsg - ExternalID_ string `json:"external_id,omitempty"` - Order_ *types.XObject `json:"order,omitempty"` + ExternalID_ string `json:"external_id,omitempty"` + Order_ *Order `json:"order,omitempty"` } // MsgOut represents a outgoing message to the session contact @@ -134,9 +133,9 @@ func (m *BaseMsg) Text() string { return m.Text_ } // Attachments returns the attachments of this message func (m *BaseMsg) Attachments() []utils.Attachment { return m.Attachments_ } -func (m *MsgIn) Order() *types.XObject { return m.Order_ } +func (m *MsgIn) Order() *Order { return m.Order_ } -func (m *MsgIn) SetOrder(order *types.XObject) { m.Order_ = order } +func (m *MsgIn) SetOrder(order *Order) { m.Order_ = order } // ExternalID returns the optional external ID of this incoming message func (m *MsgIn) ExternalID() string { return m.ExternalID_ } diff --git a/flows/order.go b/flows/order.go new file mode 100644 index 000000000..be2d54c1b --- /dev/null +++ b/flows/order.go @@ -0,0 +1,77 @@ +package flows + +import ( + "encoding/json" + + "github.com/nyaruka/gocommon/jsonx" + "github.com/nyaruka/goflow/assets" + "github.com/nyaruka/goflow/envs" + "github.com/nyaruka/goflow/excellent/types" + "github.com/nyaruka/goflow/utils" + "github.com/pkg/errors" + "github.com/shopspring/decimal" +) + +type Order struct { + CatalogID string `json:"catalog_id,omitempty"` + ProductItems []ProductItem `json:"product_items,omitempty"` + Text string `json:"text,omitempty"` +} + +type orderEnvelope struct { + CatalogID string `json:"catalog_id,omitempty"` + ProductItems []ProductItem `json:"product_items,omitempty"` + Text string `json:"text,omitempty"` +} + +type ProductItem struct { + Currency string `json:"currency,omitempty"` + ItemPrice decimal.Decimal `json:"item_price,omitempty"` + ProductRetailerID string `json:"product_retailer_id"` + Quantity int64 `json:"quantity,omitempty"` +} + +func (o *Order) Context(env envs.Environment) map[string]types.XValue { + array := make([]types.XValue, len(o.ProductItems)) + for i, p := range o.ProductItems { + array[i] = types.NewXObject(map[string]types.XValue{ + "currency": types.NewXText(p.Currency), + "item_price": types.NewXNumber(p.ItemPrice), + "product_retailer_id": types.NewXText(p.ProductRetailerID), + "quantity": types.NewXNumberFromInt64(p.Quantity), + }) + } + + return map[string]types.XValue{ + "catalog_id": types.NewXText(o.CatalogID), + "product_items": types.NewXArray(array...), + "text": types.NewXText(o.Text), + } +} + +func ReadOrder(sa SessionAssets, data json.RawMessage, missing assets.MissingCallback) (*Order, error) { + var envelope orderEnvelope + var err error + + if err = utils.UnmarshalAndValidate(data, &envelope); err != nil { + return nil, errors.Wrap(err, "unable to read order") + } + + o := &Order{ + CatalogID: envelope.CatalogID, + ProductItems: envelope.ProductItems, + Text: envelope.Text, + } + + return o, nil +} + +func (o *Order) MarshalJSON() ([]byte, error) { + oe := &orderEnvelope{ + CatalogID: o.CatalogID, + ProductItems: o.ProductItems, + Text: o.Text, + } + + return jsonx.Marshal(oe) +} diff --git a/flows/resumes/base.go b/flows/resumes/base.go index 7bbec2cd0..5c2fb26a6 100644 --- a/flows/resumes/base.go +++ b/flows/resumes/base.go @@ -37,7 +37,7 @@ type baseResume struct { environment envs.Environment contact *flows.Contact resumedOn time.Time - params *types.XObject + order *flows.Order } // creates a new base resume @@ -51,9 +51,7 @@ func (r *baseResume) Type() string { return r.type_ } func (r *baseResume) Environment() envs.Environment { return r.environment } func (r *baseResume) Contact() *flows.Contact { return r.contact } func (r *baseResume) ResumedOn() time.Time { return r.resumedOn } -func (r *baseResume) Params() *types.XObject { return r.params } - -func (r *baseResume) SetParams(params *types.XObject) { r.params = params } +func (r *baseResume) Order() *flows.Order { return r.order } // Apply applies our state changes and saves any events to the run func (r *baseResume) Apply(run flows.FlowRun, logEvent flows.EventCallback) { @@ -119,7 +117,7 @@ type baseResumeEnvelope struct { Environment json.RawMessage `json:"environment,omitempty"` Contact json.RawMessage `json:"contact,omitempty"` ResumedOn time.Time `json:"resumed_on" validate:"required"` - Params json.RawMessage `json:"params,omitempty"` + Order json.RawMessage `json:"order,omitempty"` } // ReadResume reads a resume from the given JSON @@ -152,9 +150,9 @@ func (r *baseResume) unmarshal(sessionAssets flows.SessionAssets, e *baseResumeE return errors.Wrap(err, "unable to read contact") } } - if e.Params != nil { - if r.params, err = types.ReadXObject(e.Params); err != nil { - return errors.Wrap(err, "unable to read params") + if e.Order != nil { + if r.order, err = flows.ReadOrder(sessionAssets, e.Order, missing); err != nil { + return errors.Wrap(err, "unable to read order") } } return nil @@ -177,8 +175,8 @@ func (r *baseResume) marshal(e *baseResumeEnvelope) error { return err } } - if r.params != nil { - e.Params, err = jsonx.Marshal(r.params) + if r.order != nil { + e.Order, err = jsonx.Marshal(r.order) if err != nil { return err } diff --git a/flows/resumes/msg.go b/flows/resumes/msg.go index 9424990cf..26bb4fe1b 100644 --- a/flows/resumes/msg.go +++ b/flows/resumes/msg.go @@ -64,9 +64,9 @@ func (r *MsgResume) Apply(run flows.FlowRun, logEvent flows.EventCallback) { // do base changes (contact, environment) r.baseResume.Apply(run, logEvent) - params := r.Params() - if params != nil { - r.msg.SetOrder(params) + order := r.Order() + if order != nil { + r.msg.SetOrder(order) } // update our input diff --git a/flows/triggers/msg_test.go b/flows/triggers/msg_test.go new file mode 100644 index 000000000..5833e031e --- /dev/null +++ b/flows/triggers/msg_test.go @@ -0,0 +1,109 @@ +package triggers + +import ( + "encoding/json" + "log" + "testing" + + "github.com/nyaruka/gocommon/jsonx" +) + +func TestMsgTrigger(t *testing.T) { + + var msgTrigger MsgTrigger + + err := json.Unmarshal([]byte(triggerJSON), &msgTrigger) + if err != nil { + t.Fatal(err) + } + tm, err := msgTrigger.MarshalJSON() + if err != nil { + t.Fatal(err) + } + + log.Println(string(tm)) +} + +func TestMsgTriggerMarshallJSON(t *testing.T) { + var mtEnvelop msgTriggerEnvelope + + err := json.Unmarshal([]byte(triggerJSON), &mtEnvelop) + if err != nil { + t.Fatal(err) + } + + res, err := jsonx.Marshal(mtEnvelop) + if err != nil { + t.Fatal(err) + } + log.Println(string(res)) +} + +const triggerJSON = `{ + "type": "msg", + "environment": { + "date_format": "DD-MM-YYYY", + "time_format": "tt:mm", + "timezone": "America/Argentina/Buenos_Aires", + "number_format": { + "decimal_symbol": ".", + "digit_grouping_symbol": "," + }, + "default_country": "BR", + "redaction_policy": "none", + "max_value_length": 3840 + }, + "flow": { + "uuid": "7a2236bc-4c92-4ad6-b81a-0d86d2fdedc0", + "name": "catalogo" + }, + "contact": { + "uuid": "7eed1ae9-4f7d-4211-a788-8d73ebdd518c", + "id": 389, + "name": "Roberta Moreira", + "status": "active", + "timezone": "America/Argentina/Buenos_Aires", + "created_on": "2023-10-05T19:35:25.351816Z", + "urns": [ + "whatsapp:5582999489287?channel=3065fa26-593b-4517-9318-6050165c78d7\\u0026id=387\\u0026priority=1000" + ] + }, + "params": { + "order": { + "catalog_id": "1729754620797120", + "product_items": [ + { + "currency": "BRL", + "item_price": 2.46, + "product_retailer_id": "1", + "quantity": 1 + } + ], + "text": "" + } + }, + "triggered_on": "2023-10-05T19:35:25.576204487Z", + "msg": { + "uuid": "22454ba0-b8db-440d-a1a6-1a6bb56e7add", + "id": 162145, + "urn": "whatsapp:5582999489287", + "channel": { + "uuid": "3065fa26-593b-4517-9318-6050165c78d7", + "name": "Teste Weni Cloud 5" + }, + "text": "", + "external_id": "wamid.HB1gMNTU4ODkzaaN2asaTasasYda1fa1asaOsaTA21FsQaa3I1AaaqEa2hssgWa23M0V2aCMEafY4NDYaswNDaRGOaUFBNzcwNjQysRgA=", + "order": { + "catalog_id": "1729754620797120", + "product_items": [ + { + "currency": "BRL", + "item_price": 2.46, + "product_retailer_id": "1", + "quantity": 1 + } + ], + "text": "" + } + } +}`