diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb6c30f31..1b9aea35f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ name: CI on: [push, pull_request] env: - go-version: '1.17.x' + go-version: '1.23.x' redis-version: '3.2.4' jobs: test: diff --git a/backends/rapidpro/msg.go b/backends/rapidpro/msg.go index 9bcff4b5c..6c20103f6 100644 --- a/backends/rapidpro/msg.go +++ b/backends/rapidpro/msg.go @@ -920,104 +920,143 @@ func (m *DBMsg) OrderDetailsMessage() *courier.OrderDetailsMessage { return nil } - if interactionType, ok := metadata["interaction_type"].(string); ok && interactionType == "order_details" { - if orderDetailsMessageData, ok := metadata["order_details_message"].(map[string]interface{}); ok { - orderDetailsMessage := &courier.OrderDetailsMessage{} - if referenceID, ok := orderDetailsMessageData["reference_id"].(string); ok { - orderDetailsMessage.ReferenceID = referenceID + if orderDetailsMessageData, ok := metadata["order_details_message"].(map[string]interface{}); ok { + orderDetailsMessage := &courier.OrderDetailsMessage{} + if referenceID, ok := orderDetailsMessageData["reference_id"].(string); ok { + orderDetailsMessage.ReferenceID = referenceID + } + if paymentSettings, ok := orderDetailsMessageData["payment_settings"].(map[string]interface{}); ok { + orderDetailsMessage.PaymentSettings = courier.OrderPaymentSettings{} + if payment_type, ok := paymentSettings["type"].(string); ok { + orderDetailsMessage.PaymentSettings.Type = payment_type + } + if payment_link, ok := paymentSettings["payment_link"].(string); ok { + orderDetailsMessage.PaymentSettings.PaymentLink = payment_link } - if paymentSettings, ok := orderDetailsMessageData["payment_settings"].(map[string]interface{}); ok { - orderDetailsMessage.PaymentSettings = courier.OrderPaymentSettings{} - if payment_type, ok := paymentSettings["type"].(string); ok { - orderDetailsMessage.PaymentSettings.Type = payment_type + if pix_config, ok := paymentSettings["pix_config"].(map[string]interface{}); ok { + orderDetailsMessage.PaymentSettings.PixConfig = courier.OrderPixConfig{} + if pix_config_key, ok := pix_config["key"].(string); ok { + orderDetailsMessage.PaymentSettings.PixConfig.Key = pix_config_key } - if payment_link, ok := paymentSettings["payment_link"].(string); ok { - orderDetailsMessage.PaymentSettings.PaymentLink = payment_link + if pix_config_key_type, ok := pix_config["key_type"].(string); ok { + orderDetailsMessage.PaymentSettings.PixConfig.KeyType = pix_config_key_type } - if pix_config, ok := paymentSettings["pix_config"].(map[string]interface{}); ok { - orderDetailsMessage.PaymentSettings.PixConfig = courier.OrderPixConfig{} - if pix_config_key, ok := pix_config["key"].(string); ok { - orderDetailsMessage.PaymentSettings.PixConfig.Key = pix_config_key - } - if pix_config_key_type, ok := pix_config["key_type"].(string); ok { - orderDetailsMessage.PaymentSettings.PixConfig.KeyType = pix_config_key_type - } - if pix_config_merchant_name, ok := pix_config["merchant_name"].(string); ok { - orderDetailsMessage.PaymentSettings.PixConfig.MerchantName = pix_config_merchant_name - } - if pix_config_code, ok := pix_config["code"].(string); ok { - orderDetailsMessage.PaymentSettings.PixConfig.Code = pix_config_code - } + if pix_config_merchant_name, ok := pix_config["merchant_name"].(string); ok { + orderDetailsMessage.PaymentSettings.PixConfig.MerchantName = pix_config_merchant_name + } + if pix_config_code, ok := pix_config["code"].(string); ok { + orderDetailsMessage.PaymentSettings.PixConfig.Code = pix_config_code } } - if totalAmount, ok := orderDetailsMessageData["total_amount"].(float64); ok { - orderDetailsMessage.TotalAmount = int(totalAmount) - } - if orderData, ok := orderDetailsMessageData["order"].(map[string]interface{}); ok { - orderDetailsMessage.Order = courier.Order{} - if itemsData, ok := orderData["items"].([]interface{}); ok { - orderDetailsMessage.Order.Items = make([]courier.OrderItem, len(itemsData)) - for i, item := range itemsData { - if itemMap, ok := item.(map[string]interface{}); ok { - itemAmount := itemMap["amount"].(map[string]interface{}) - item := courier.OrderItem{ - RetailerID: itemMap["retailer_id"].(string), - Name: itemMap["name"].(string), - Quantity: int(itemMap["quantity"].(float64)), - Amount: courier.OrderAmountWithOffset{ - Value: int(itemAmount["value"].(float64)), - Offset: int(itemAmount["offset"].(float64)), - }, - } + } + if totalAmount, ok := orderDetailsMessageData["total_amount"].(float64); ok { + orderDetailsMessage.TotalAmount = int(totalAmount) + } + if orderData, ok := orderDetailsMessageData["order"].(map[string]interface{}); ok { + orderDetailsMessage.Order = courier.Order{} + if itemsData, ok := orderData["items"].([]interface{}); ok { + orderDetailsMessage.Order.Items = make([]courier.OrderItem, len(itemsData)) + for i, item := range itemsData { + if itemMap, ok := item.(map[string]interface{}); ok { + itemAmount := itemMap["amount"].(map[string]interface{}) + item := courier.OrderItem{ + RetailerID: itemMap["retailer_id"].(string), + Name: itemMap["name"].(string), + Quantity: int(itemMap["quantity"].(float64)), + Amount: courier.OrderAmountWithOffset{ + Value: int(itemAmount["value"].(float64)), + Offset: int(itemAmount["offset"].(float64)), + }, + } - if itemMap["sale_amount"] != nil { - saleAmount := itemMap["sale_amount"].(map[string]interface{}) - item.SaleAmount = &courier.OrderAmountWithOffset{ - Value: int(saleAmount["value"].(float64)), - Offset: int(saleAmount["offset"].(float64)), - } + if itemMap["sale_amount"] != nil { + saleAmount := itemMap["sale_amount"].(map[string]interface{}) + item.SaleAmount = &courier.OrderAmountWithOffset{ + Value: int(saleAmount["value"].(float64)), + Offset: int(saleAmount["offset"].(float64)), } - - orderDetailsMessage.Order.Items[i] = item } + + orderDetailsMessage.Order.Items[i] = item } } - if subtotal, ok := orderData["subtotal"].(float64); ok { - orderDetailsMessage.Order.Subtotal = int(subtotal) + } + if subtotal, ok := orderData["subtotal"].(float64); ok { + orderDetailsMessage.Order.Subtotal = int(subtotal) + } + if taxData, ok := orderData["tax"].(map[string]interface{}); ok { + orderDetailsMessage.Order.Tax = courier.OrderAmountWithDescription{} + if value, ok := taxData["value"].(float64); ok { + orderDetailsMessage.Order.Tax.Value = int(value) } - if taxData, ok := orderData["tax"].(map[string]interface{}); ok { - orderDetailsMessage.Order.Tax = courier.OrderAmountWithDescription{} - if value, ok := taxData["value"].(float64); ok { - orderDetailsMessage.Order.Tax.Value = int(value) - } - if description, ok := taxData["description"].(string); ok { - orderDetailsMessage.Order.Tax.Description = description - } + if description, ok := taxData["description"].(string); ok { + orderDetailsMessage.Order.Tax.Description = description } - if shippingData, ok := orderData["shipping"].(map[string]interface{}); ok { - orderDetailsMessage.Order.Shipping = courier.OrderAmountWithDescription{} - if value, ok := shippingData["value"].(float64); ok { - orderDetailsMessage.Order.Shipping.Value = int(value) - } - if description, ok := shippingData["description"].(string); ok { - orderDetailsMessage.Order.Shipping.Description = description - } + } + if shippingData, ok := orderData["shipping"].(map[string]interface{}); ok { + orderDetailsMessage.Order.Shipping = courier.OrderAmountWithDescription{} + if value, ok := shippingData["value"].(float64); ok { + orderDetailsMessage.Order.Shipping.Value = int(value) } - if discountData, ok := orderData["discount"].(map[string]interface{}); ok { - orderDetailsMessage.Order.Discount = courier.OrderDiscount{} - if value, ok := discountData["value"].(float64); ok { - orderDetailsMessage.Order.Discount.Value = int(value) - } - if description, ok := discountData["description"].(string); ok { - orderDetailsMessage.Order.Discount.Description = description - } - if programName, ok := discountData["program_name"].(string); ok { - orderDetailsMessage.Order.Discount.ProgramName = programName - } + if description, ok := shippingData["description"].(string); ok { + orderDetailsMessage.Order.Shipping.Description = description + } + } + if discountData, ok := orderData["discount"].(map[string]interface{}); ok { + orderDetailsMessage.Order.Discount = courier.OrderDiscount{} + if value, ok := discountData["value"].(float64); ok { + orderDetailsMessage.Order.Discount.Value = int(value) + } + if description, ok := discountData["description"].(string); ok { + orderDetailsMessage.Order.Discount.Description = description + } + if programName, ok := discountData["program_name"].(string); ok { + orderDetailsMessage.Order.Discount.ProgramName = programName + } + } + } + return orderDetailsMessage + } + + return nil +} + +func (m *DBMsg) Buttons() []courier.ButtonComponent { + if m.Metadata_ == nil { + return nil + } + + var metadata map[string]interface{} + err := json.Unmarshal(m.Metadata_, &metadata) + if err != nil { + return nil + } + + if metadata == nil { + return nil + } + + if buttonsData, ok := metadata["buttons"].([]interface{}); ok { + buttons := make([]courier.ButtonComponent, len(buttonsData)) + for i, button := range buttonsData { + buttonMap := button.(map[string]interface{}) + buttons[i] = courier.ButtonComponent{ + SubType: buttonMap["sub_type"].(string), + Parameters: []courier.ButtonParam{}, + } + + if buttonMap["parameters"] != nil { + parameters := buttonMap["parameters"].([]interface{}) + for _, parameter := range parameters { + parameterMap := parameter.(map[string]interface{}) + buttons[i].Parameters = append(buttons[i].Parameters, courier.ButtonParam{ + Type: parameterMap["type"].(string), + Text: parameterMap["text"].(string), + }) } } - return orderDetailsMessage } + return buttons } return nil diff --git a/go.mod b/go.mod index df28926d5..2e2fdc545 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ require ( github.com/antchfx/xmlquery v0.0.0-20181223105952-355641961c92 github.com/antchfx/xpath v0.0.0-20181208024549-4bbdf6db12aa // indirect github.com/aws/aws-sdk-go v1.40.56 - github.com/buger/jsonparser v0.0.0-20180318095312-2cac668e8456 + github.com/buger/jsonparser v1.1.1 github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261 // indirect github.com/dghubble/oauth1 v0.4.0 github.com/evalphobia/logrus_sentry v0.4.6 @@ -72,6 +72,6 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -go 1.17 +go 1.23 replace github.com/nyaruka/gocommon => github.com/Ilhasoft/gocommon v1.16.2-weni diff --git a/go.sum b/go.sum index daf318e8d..4dfcc7aae 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/antchfx/xpath v0.0.0-20181208024549-4bbdf6db12aa h1:lL66YnJWy1tHlhjSx github.com/antchfx/xpath v0.0.0-20181208024549-4bbdf6db12aa/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= github.com/aws/aws-sdk-go v1.40.56 h1:FM2yjR0UUYFzDTMx+mH9Vyw1k1EUUxsAFzk+BjkzANA= github.com/aws/aws-sdk-go v1.40.56/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= -github.com/buger/jsonparser v0.0.0-20180318095312-2cac668e8456 h1:SnUWpAH4lEUoS86woR12h21VMUbDe+DYp88V646wwMI= -github.com/buger/jsonparser v0.0.0-20180318095312-2cac668e8456/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261 h1:6/yVvBsKeAw05IUj4AzvrxaCnDjN4nUqKjW9+w5wixg= github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/handlers/facebookapp/facebookapp.go b/handlers/facebookapp/facebookapp.go index 29a6e38a0..401a5b36c 100644 --- a/handlers/facebookapp/facebookapp.go +++ b/handlers/facebookapp/facebookapp.go @@ -1341,18 +1341,23 @@ type wacMTButton struct { } `json:"reply" validate:"required"` } +type wacMTAction struct { + OrderDetails *wacOrderDetails `json:"order_details,omitempty"` +} + type wacParam struct { - Type string `json:"type"` - Text string `json:"text,omitempty"` - Image *wacMTMedia `json:"image,omitempty"` - Document *wacMTMedia `json:"document,omitempty"` - Video *wacMTMedia `json:"video,omitempty"` + Type string `json:"type"` + Text string `json:"text,omitempty"` + Image *wacMTMedia `json:"image,omitempty"` + Document *wacMTMedia `json:"document,omitempty"` + Video *wacMTMedia `json:"video,omitempty"` + Action *wacMTAction `json:"action,omitempty"` } type wacComponent struct { Type string `json:"type"` SubType string `json:"sub_type,omitempty"` - Index string `json:"index,omitempty"` + Index *int `json:"index,omitempty"` Params []*wacParam `json:"parameters"` } @@ -1372,7 +1377,11 @@ type wacTemplate struct { Components []*wacComponent `json:"components"` } -type wacInteractive struct { +type wacInteractiveActionParams interface { + ~map[string]any | wacOrderDetails +} + +type wacInteractive[P wacInteractiveActionParams] struct { Type string `json:"type"` Header *struct { Type string `json:"type"` @@ -1388,17 +1397,17 @@ type wacInteractive struct { Text string `json:"text,omitempty"` } `json:"footer,omitempty"` Action *struct { - Button string `json:"button,omitempty"` - Sections []wacMTSection `json:"sections,omitempty"` - Buttons []wacMTButton `json:"buttons,omitempty"` - CatalogID string `json:"catalog_id,omitempty"` - ProductRetailerID string `json:"product_retailer_id,omitempty"` - Name string `json:"name,omitempty"` - Parameters map[string]interface{} `json:"parameters,omitempty"` + Button string `json:"button,omitempty"` + Sections []wacMTSection `json:"sections,omitempty"` + Buttons []wacMTButton `json:"buttons,omitempty"` + CatalogID string `json:"catalog_id,omitempty"` + ProductRetailerID string `json:"product_retailer_id,omitempty"` + Name string `json:"name,omitempty"` + Parameters P `json:"parameters,omitempty"` } `json:"action,omitempty"` } -type wacMTPayload struct { +type wacMTPayload[P wacInteractiveActionParams] struct { MessagingProduct string `json:"messaging_product"` RecipientType string `json:"recipient_type"` To string `json:"to"` @@ -1412,7 +1421,7 @@ type wacMTPayload struct { Video *wacMTMedia `json:"video,omitempty"` Sticker *wacMTMedia `json:"sticker,omitempty"` - Interactive *wacInteractive `json:"interactive,omitempty"` + Interactive *wacInteractive[P] `json:"interactive,omitempty"` Template *wacTemplate `json:"template,omitempty"` } @@ -1435,6 +1444,33 @@ type wacMTProductItem struct { ProductRetailerID string `json:"product_retailer_id" validate:"required"` } +type wacOrderDetailsPixDynamicCode struct { + Code string `json:"code" validate:"required"` + MerchantName string `json:"merchant_name" validate:"required"` + Key string `json:"key" validate:"required"` + KeyType string `json:"key_type" validate:"required"` +} + +type wacOrderDetailsPaymentLink struct { + URI string `json:"uri" validate:"required"` +} + +type wacOrderDetailsPaymentSetting struct { + Type string `json:"type" validate:"required"` + PaymentLink *wacOrderDetailsPaymentLink `json:"payment_link,omitempty"` + PixDynamicCode *wacOrderDetailsPixDynamicCode `json:"pix_dynamic_code,omitempty"` +} + +type wacOrderDetails struct { + ReferenceID string `json:"reference_id" validate:"required"` + Type string `json:"type" validate:"required"` + PaymentType string `json:"payment_type" validate:"required"` + PaymentSettings []wacOrderDetailsPaymentSetting `json:"payment_settings" validate:"required"` + Currency string `json:"currency" validate:"required"` + TotalAmount wacAmountWithOffset `json:"total_amount" validate:"required"` + Order wacOrder `json:"order" validate:"required"` +} + type wacOrder struct { Status string `json:"status" validate:"required"` CatalogID string `json:"catalog_id,omitempty"` @@ -1488,11 +1524,10 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) } qrs := msg.QuickReplies() - var payloadAudio wacMTPayload + var payloadAudio wacMTPayload[map[string]any] for i := 0; i < len(msgParts)+len(msg.Attachments()); i++ { - - payload := wacMTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path()} + payload := wacMTPayload[map[string]any]{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path()} // do we have a template? var templating *MsgTemplating @@ -1559,6 +1594,35 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) payload.Template.Components = append(payload.Template.Components, header) } + if msg.OrderDetailsMessage() != nil { + index := 0 + button := &wacComponent{Type: "button", SubType: "order_details", Index: &index} + + paymentSettings, catalogID, orderTax, orderShipping, orderDiscount := mountOrderInfo(msg) + + param := wacParam{ + Type: "action", + Action: &wacMTAction{ + OrderDetails: mountOrderDetails(msg, paymentSettings, catalogID, orderTax, orderShipping, orderDiscount), + }, + } + + button.Params = append(button.Params, ¶m) + payload.Template.Components = append(payload.Template.Components, button) + } + + if len(msg.Buttons()) > 0 { + for i, button := range msg.Buttons() { + buttonComponent := &wacComponent{Type: "button", SubType: button.SubType, Index: &i} + + for _, parameter := range button.Parameters { + buttonComponent.Params = append(buttonComponent.Params, &wacParam{Type: parameter.Type, Text: parameter.Text}) + } + + payload.Template.Components = append(payload.Template.Components, buttonComponent) + } + } + } else { if i < (len(msgParts) + len(msg.Attachments()) - 1) { if strings.Contains(msgParts[i-len(msg.Attachments())], "https://") || strings.Contains(msgParts[i-len(msg.Attachments())], "http://") { @@ -1575,7 +1639,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) payload.Type = "interactive" // We can use buttons if len(qrs) > 0 && len(qrs) <= 3 { - interactive := wacInteractive{ + interactive := wacInteractive[map[string]any]{ Type: "button", Body: struct { Text string "json:\"text\"" @@ -1625,7 +1689,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) }{Buttons: btns} payload.Interactive = &interactive } else if len(qrs) <= 10 || len(msg.ListMessage().ListItems) > 0 { - interactive := wacInteractive{ + interactive := wacInteractive[map[string]any]{ Type: "list", Body: struct { Text string "json:\"text\"" @@ -1699,7 +1763,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) } } else if msg.InteractionType() == "location" { payload.Type = "interactive" - interactive := wacInteractive{ + interactive := wacInteractive[map[string]any]{ Type: "location_request_message", Body: struct { Text string "json:\"text\"" @@ -1719,7 +1783,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) } else if msg.InteractionType() == "cta_url" { if ctaMessage := msg.CTAMessage(); ctaMessage != nil { payload.Type = "interactive" - interactive := wacInteractive{ + interactive := wacInteractive[map[string]any]{ Type: "cta_url", Body: struct { Text string "json:\"text\"" @@ -1760,7 +1824,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) } else if msg.InteractionType() == "flow_msg" { if flowMessage := msg.FlowMessage(); flowMessage != nil { payload.Type = "interactive" - interactive := wacInteractive{ + interactive := wacInteractive[map[string]any]{ Type: "flow", Body: struct { Text string "json:\"text\"" @@ -1810,101 +1874,24 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) if orderDetails := msg.OrderDetailsMessage(); orderDetails != nil { payload.Type = "interactive" - paymentSettings := make([]map[string]interface{}, 0) + paymentSettings, catalogID, orderTax, orderShipping, orderDiscount := mountOrderInfo(msg) - if orderDetails.PaymentSettings.PaymentLink != "" { - paymentSettings = append(paymentSettings, map[string]interface{}{ - "type": "payment_link", - "payment_link": map[string]interface{}{ - "uri": orderDetails.PaymentSettings.PaymentLink, - }, - }) - } - - if orderDetails.PaymentSettings.PixConfig.Code != "" { - paymentSettings = append(paymentSettings, map[string]interface{}{ - "type": "pix_dynamic_code", - "pix_dynamic_code": map[string]interface{}{ - "code": orderDetails.PaymentSettings.PixConfig.Code, - "merchant_name": orderDetails.PaymentSettings.PixConfig.MerchantName, - "key": orderDetails.PaymentSettings.PixConfig.Key, - "key_type": orderDetails.PaymentSettings.PixConfig.KeyType, - }, - }) - } - - strCatalogID := msg.Channel().StringConfigForKey("catalog_id", "") - var catalogID *string - if strCatalogID != "" { - catalogID = &strCatalogID - } - - orderTax := wacAmountWithOffset{ - Value: 0, - Offset: 100, - Description: orderDetails.Order.Tax.Description, - } - if orderDetails.Order.Tax.Value > 0 { - orderTax.Value = orderDetails.Order.Tax.Value - } - - var orderShipping *wacAmountWithOffset - var orderDiscount *wacAmountWithOffset - if orderDetails.Order.Shipping.Value > 0 { - orderShipping = &wacAmountWithOffset{ - Value: orderDetails.Order.Shipping.Value, - Offset: 100, - Description: orderDetails.Order.Shipping.Description, - } - } - - if orderDetails.Order.Discount.Value > 0 { - orderDiscount = &wacAmountWithOffset{ - Value: orderDetails.Order.Discount.Value, - Offset: 100, - Description: orderDetails.Order.Discount.Description, - DiscountProgramName: orderDetails.Order.Discount.ProgramName, - } - } - - interactive := wacInteractive{ + interactive := wacInteractive[wacOrderDetails]{ Type: "order_details", Body: struct { Text string "json:\"text\"" }{Text: msgParts[i-len(msg.Attachments())]}, Action: &struct { - Button string "json:\"button,omitempty\"" - Sections []wacMTSection "json:\"sections,omitempty\"" - Buttons []wacMTButton "json:\"buttons,omitempty\"" - CatalogID string "json:\"catalog_id,omitempty\"" - ProductRetailerID string "json:\"product_retailer_id,omitempty\"" - Name string "json:\"name,omitempty\"" - Parameters map[string]interface{} "json:\"parameters,omitempty\"" + Button string "json:\"button,omitempty\"" + Sections []wacMTSection "json:\"sections,omitempty\"" + Buttons []wacMTButton "json:\"buttons,omitempty\"" + CatalogID string "json:\"catalog_id,omitempty\"" + ProductRetailerID string "json:\"product_retailer_id,omitempty\"" + Name string "json:\"name,omitempty\"" + Parameters wacOrderDetails "json:\"parameters,omitempty\"" }{ - Name: "review_and_pay", - Parameters: map[string]interface{}{ - "reference_id": orderDetails.ReferenceID, - "type": orderDetails.PaymentSettings.Type, - "payment_type": "br", - "payment_settings": paymentSettings, - "currency": "BRL", - "total_amount": wacAmountWithOffset{ - Value: orderDetails.TotalAmount, - Offset: 100, - }, - "order": wacOrder{ - Status: "pending", - CatalogID: *catalogID, - Items: orderDetails.Order.Items, - Subtotal: wacAmountWithOffset{ - Value: orderDetails.Order.Subtotal, - Offset: 100, - }, - Tax: orderTax, - Shipping: orderShipping, - Discount: orderDiscount, - }, - }, + Name: "review_and_pay", + Parameters: *mountOrderDetails(msg, paymentSettings, catalogID, orderTax, orderShipping, orderDiscount), }, } if msg.Footer() != "" { @@ -1913,7 +1900,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) }{Text: parseBacklashes(msg.Footer())} } - payload.Interactive = &interactive + payload.Interactive = castInteractive[wacOrderDetails, map[string]any](interactive) } } else { // this is still a msg part @@ -1996,7 +1983,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) return nil, fmt.Errorf("message body cannot be empty") } - interactive := wacInteractive{ + interactive := wacInteractive[map[string]any]{ Type: "button", Body: struct { Text string "json:\"text\"" @@ -2054,7 +2041,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) if i == 0 { zeroIndex = true } - payloadAudio = wacMTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &wacMTMedia{ID: mediaID, Link: attURL}} + payloadAudio = wacMTPayload[map[string]any]{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &wacMTMedia{ID: mediaID, Link: attURL}} status, _, err := requestWAC(payloadAudio, token, msg, status, wacPhoneURL, zeroIndex) if err != nil { return status, nil @@ -2090,7 +2077,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) }{Text: parseBacklashes(msg.Footer())} } } else if len(qrs) <= 10 || len(msg.ListMessage().ListItems) > 0 { - interactive := wacInteractive{ + interactive := wacInteractive[map[string]any]{ Type: "list", Body: struct { Text string "json:\"text\"" @@ -2153,7 +2140,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) return nil, fmt.Errorf("too many quick replies WAC supports only up to 10 quick replies") } } else if msg.InteractionType() == "location" { - interactive := wacInteractive{Type: "location_request_message", Body: struct { + interactive := wacInteractive[map[string]any]{Type: "location_request_message", Body: struct { Text string "json:\"text\"" }{Text: msgParts[i-len(msg.Attachments())]}, Action: &struct { Button string "json:\"button,omitempty\"" @@ -2168,7 +2155,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) payload.Interactive = &interactive } else if msg.InteractionType() == "cta_url" { if ctaMessage := msg.CTAMessage(); ctaMessage != nil { - interactive := wacInteractive{ + interactive := wacInteractive[map[string]any]{ Type: "cta_url", Body: struct { Text string "json:\"text\"" @@ -2209,7 +2196,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) } } else if msg.InteractionType() == "flow_msg" { if flowMessage := msg.FlowMessage(); flowMessage != nil { - interactive := wacInteractive{ + interactive := wacInteractive[map[string]any]{ Type: "flow", Body: struct { Text string "json:\"text\"" @@ -2261,100 +2248,24 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) hasCaption = true payload.Type = "interactive" - paymentSettings := make([]map[string]interface{}, 0) + paymentSettings, catalogID, orderTax, orderShipping, orderDiscount := mountOrderInfo(msg) - if orderDetails.PaymentSettings.PaymentLink != "" { - paymentSettings = append(paymentSettings, map[string]interface{}{ - "type": "payment_link", - "payment_link": map[string]interface{}{ - "uri": orderDetails.PaymentSettings.PaymentLink, - }, - }) - } - if orderDetails.PaymentSettings.PixConfig.Code != "" { - paymentSettings = append(paymentSettings, map[string]interface{}{ - "type": "pix_dynamic_code", - "pix_dynamic_code": map[string]interface{}{ - "code": orderDetails.PaymentSettings.PixConfig.Code, - "merchant_name": orderDetails.PaymentSettings.PixConfig.MerchantName, - "key": orderDetails.PaymentSettings.PixConfig.Key, - "key_type": orderDetails.PaymentSettings.PixConfig.KeyType, - }, - }) - } - - strCatalogID := msg.Channel().StringConfigForKey("catalog_id", "") - var catalogID *string - if strCatalogID != "" { - catalogID = &strCatalogID - } - - orderTax := wacAmountWithOffset{ - Value: 0, - Offset: 100, - Description: orderDetails.Order.Tax.Description, - } - if orderDetails.Order.Tax.Value > 0 { - orderTax.Value = orderDetails.Order.Tax.Value - } - - var orderShipping *wacAmountWithOffset - var orderDiscount *wacAmountWithOffset - if orderDetails.Order.Shipping.Value > 0 { - orderShipping = &wacAmountWithOffset{ - Value: orderDetails.Order.Shipping.Value, - Offset: 100, - Description: orderDetails.Order.Shipping.Description, - } - } - - if orderDetails.Order.Discount.Value > 0 { - orderDiscount = &wacAmountWithOffset{ - Value: orderDetails.Order.Discount.Value, - Offset: 100, - Description: orderDetails.Order.Discount.Description, - DiscountProgramName: orderDetails.Order.Discount.ProgramName, - } - } - - interactive := wacInteractive{ + interactive := wacInteractive[wacOrderDetails]{ Type: "order_details", Body: struct { Text string "json:\"text\"" }{Text: msgParts[i]}, Action: &struct { - Button string "json:\"button,omitempty\"" - Sections []wacMTSection "json:\"sections,omitempty\"" - Buttons []wacMTButton "json:\"buttons,omitempty\"" - CatalogID string "json:\"catalog_id,omitempty\"" - ProductRetailerID string "json:\"product_retailer_id,omitempty\"" - Name string "json:\"name,omitempty\"" - Parameters map[string]interface{} "json:\"parameters,omitempty\"" + Button string "json:\"button,omitempty\"" + Sections []wacMTSection "json:\"sections,omitempty\"" + Buttons []wacMTButton "json:\"buttons,omitempty\"" + CatalogID string "json:\"catalog_id,omitempty\"" + ProductRetailerID string "json:\"product_retailer_id,omitempty\"" + Name string "json:\"name,omitempty\"" + Parameters wacOrderDetails "json:\"parameters,omitempty\"" }{ - Name: "review_and_pay", - Parameters: map[string]interface{}{ - "reference_id": orderDetails.ReferenceID, - "type": orderDetails.PaymentSettings.Type, - "payment_type": "br", - "payment_settings": paymentSettings, - "currency": "BRL", - "total_amount": wacAmountWithOffset{ - Value: orderDetails.TotalAmount, - Offset: 100, - }, - "order": wacOrder{ - Status: "pending", - CatalogID: *catalogID, - Items: orderDetails.Order.Items, - Subtotal: wacAmountWithOffset{ - Value: orderDetails.Order.Subtotal, - Offset: 100, - }, - Tax: orderTax, - Shipping: orderShipping, - Discount: orderDiscount, - }, - }, + Name: "review_and_pay", + Parameters: *mountOrderDetails(msg, paymentSettings, catalogID, orderTax, orderShipping, orderDiscount), }, } if msg.Footer() != "" { @@ -2380,7 +2291,8 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) return nil, fmt.Errorf("interactive order details message does not support attachments other than images") } } - payload.Interactive = &interactive + + payload.Interactive = castInteractive[wacOrderDetails, map[string]any](interactive) } } else { // this is still a msg part @@ -2431,7 +2343,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) return status, errors.New("Catalog ID not found in channel config") } - payload := wacMTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path()} + payload := wacMTPayload[map[string]any]{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path()} payload.Type = "interactive" @@ -2457,7 +2369,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) interactiveType = InteractiveProductSingleType } - interactive := wacInteractive{ + interactive := wacInteractive[map[string]any]{ Type: interactiveType, } @@ -2599,7 +2511,114 @@ func parseBacklashes(baseText string) string { return text } -func requestWAC(payload wacMTPayload, accessToken string, msg courier.Msg, status courier.MsgStatus, wacPhoneURL *url.URL, zeroIndex bool) (courier.MsgStatus, *wacMTResponse, error) { +func castInteractive[I, O wacInteractiveActionParams](interactive wacInteractive[I]) *wacInteractive[O] { + interactiveJSON, _ := json.Marshal(interactive) + interactiveMap := wacInteractive[O]{} + json.Unmarshal(interactiveJSON, &interactiveMap) + return &interactiveMap +} + +func mountOrderDetails(msg courier.Msg, paymentSettings []wacOrderDetailsPaymentSetting, catalogID *string, orderTax wacAmountWithOffset, orderShipping *wacAmountWithOffset, orderDiscount *wacAmountWithOffset) *wacOrderDetails { + return &wacOrderDetails{ + ReferenceID: msg.OrderDetailsMessage().ReferenceID, + Type: msg.OrderDetailsMessage().PaymentSettings.Type, + PaymentType: "br", + PaymentSettings: paymentSettings, + Currency: "BRL", + TotalAmount: wacAmountWithOffset{ + Value: msg.OrderDetailsMessage().TotalAmount, + Offset: 100, + }, + Order: wacOrder{ + Status: "pending", + CatalogID: *catalogID, + Items: msg.OrderDetailsMessage().Order.Items, + Subtotal: wacAmountWithOffset{ + Value: msg.OrderDetailsMessage().Order.Subtotal, + Offset: 100, + }, + Tax: orderTax, + Shipping: orderShipping, + Discount: orderDiscount, + }, + } +} + +func mountOrderPaymentSettings(orderDetails *courier.OrderDetailsMessage) []wacOrderDetailsPaymentSetting { + paymentSettings := make([]wacOrderDetailsPaymentSetting, 0) + + if orderDetails.PaymentSettings.PaymentLink != "" { + paymentSettings = append(paymentSettings, wacOrderDetailsPaymentSetting{ + Type: "payment_link", + PaymentLink: &wacOrderDetailsPaymentLink{ + URI: orderDetails.PaymentSettings.PaymentLink, + }, + }) + } + + if orderDetails.PaymentSettings.PixConfig.Code != "" { + paymentSettings = append(paymentSettings, wacOrderDetailsPaymentSetting{ + Type: "pix_dynamic_code", + PixDynamicCode: &wacOrderDetailsPixDynamicCode{ + Code: orderDetails.PaymentSettings.PixConfig.Code, + MerchantName: orderDetails.PaymentSettings.PixConfig.MerchantName, + Key: orderDetails.PaymentSettings.PixConfig.Key, + KeyType: orderDetails.PaymentSettings.PixConfig.KeyType, + }, + }) + } + + return paymentSettings +} + +func mountOrderInfo(msg courier.Msg) ([]wacOrderDetailsPaymentSetting, *string, wacAmountWithOffset, *wacAmountWithOffset, *wacAmountWithOffset) { + + paymentSettings := mountOrderPaymentSettings(msg.OrderDetailsMessage()) + + strCatalogID := msg.Channel().StringConfigForKey("catalog_id", "") + var catalogID *string + if strCatalogID != "" { + catalogID = &strCatalogID + } + + orderTax, orderShipping, orderDiscount := mountOrderTaxShippingDiscount(msg.OrderDetailsMessage()) + + return paymentSettings, catalogID, orderTax, orderShipping, orderDiscount +} + +func mountOrderTaxShippingDiscount(orderDetails *courier.OrderDetailsMessage) (wacAmountWithOffset, *wacAmountWithOffset, *wacAmountWithOffset) { + orderTax := wacAmountWithOffset{ + Value: 0, + Offset: 100, + Description: orderDetails.Order.Tax.Description, + } + if orderDetails.Order.Tax.Value > 0 { + orderTax.Value = orderDetails.Order.Tax.Value + } + + var orderShipping *wacAmountWithOffset + var orderDiscount *wacAmountWithOffset + if orderDetails.Order.Shipping.Value > 0 { + orderShipping = &wacAmountWithOffset{ + Value: orderDetails.Order.Shipping.Value, + Offset: 100, + Description: orderDetails.Order.Shipping.Description, + } + } + + if orderDetails.Order.Discount.Value > 0 { + orderDiscount = &wacAmountWithOffset{ + Value: orderDetails.Order.Discount.Value, + Offset: 100, + Description: orderDetails.Order.Discount.Description, + DiscountProgramName: orderDetails.Order.Discount.ProgramName, + } + } + + return orderTax, orderShipping, orderDiscount +} + +func requestWAC[P wacInteractiveActionParams](payload wacMTPayload[P], accessToken string, msg courier.Msg, status courier.MsgStatus, wacPhoneURL *url.URL, zeroIndex bool) (courier.MsgStatus, *wacMTResponse, error) { jsonBody, err := json.Marshal(payload) if err != nil { return status, &wacMTResponse{}, err diff --git a/handlers/facebookapp/facebookapp_test.go b/handlers/facebookapp/facebookapp_test.go index 4e54a30f4..a3156f9a9 100644 --- a/handlers/facebookapp/facebookapp_test.go +++ b/handlers/facebookapp/facebookapp_test.go @@ -873,7 +873,23 @@ var SendTestCasesWAC = []ChannelSendTestCase{ Status: "W", ExternalID: "157b5e14568e8", Attachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, ResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 201, - RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"order_details","header":{"type":"image","image":{"link":"https://foo.bar/image.jpg"}},"body":{"text":"msg text"},"footer":{"text":"footer text"},"action":{"name":"review_and_pay","parameters":{"currency":"BRL","order":{"status":"pending","catalog_id":"c4t4l0g-1D","items":[{"retailer_id":"789236789","name":"item 1","quantity":2,"amount":{"value":200,"offset":100}},{"retailer_id":"59016733","name":"item 2","quantity":9,"amount":{"value":4000,"offset":100},"sale_amount":{"value":2000,"offset":100}}],"subtotal":{"value":36400,"offset":100},"tax":{"value":500,"offset":100,"description":"tax description"},"shipping":{"value":900,"offset":100,"description":"shipping description"},"discount":{"value":1000,"offset":100,"description":"discount description","discount_program_name":"discount program name"}},"payment_settings":[{"payment_link":{"uri":"https://foo.bar"},"type":"payment_link"},{"pix_dynamic_code":{"code":"pix-code","key":"pix-key","key_type":"EMAIL","merchant_name":"merchant name"},"type":"pix_dynamic_code"}],"payment_type":"br","reference_id":"220788123125","total_amount":{"value":18200,"offset":100},"type":"digital-goods"}}}}`, + RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"order_details","header":{"type":"image","image":{"link":"https://foo.bar/image.jpg"}},"body":{"text":"msg text"},"footer":{"text":"footer text"},"action":{"name":"review_and_pay","parameters":{"currency":"BRL","order":{"catalog_id":"c4t4l0g-1D","discount":{"description":"discount description","discount_program_name":"discount program name","offset":100,"value":1000},"items":[{"amount":{"offset":100,"value":200},"name":"item 1","quantity":2,"retailer_id":"789236789"},{"amount":{"offset":100,"value":4000},"name":"item 2","quantity":9,"retailer_id":"59016733","sale_amount":{"offset":100,"value":2000}}],"shipping":{"description":"shipping description","offset":100,"value":900},"status":"pending","subtotal":{"offset":100,"value":36400},"tax":{"description":"tax description","offset":100,"value":500}},"payment_settings":[{"payment_link":{"uri":"https://foo.bar"},"type":"payment_link"},{"pix_dynamic_code":{"code":"pix-code","key":"pix-key","key_type":"EMAIL","merchant_name":"merchant name"},"type":"pix_dynamic_code"}],"payment_type":"br","reference_id":"220788123125","total_amount":{"offset":100,"value":18200},"type":"digital-goods"}}}}`, + SendPrep: setSendURL}, + {Label: "Message Template - Order Details", + Text: "templated message", + URN: "whatsapp:250788123123", + Status: "W", ExternalID: "157b5e14568e8", + Metadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "language": "eng", "variables": ["Chef", "tomorrow"]},"order_details_message":{"reference_id":"220788123125","total_amount":18200,"order":{"catalog_id":"14578923723","items":[{"retailer_id":"789236789","name":"item 1","amount":{"offset":100,"value":200},"quantity":2},{"retailer_id":"59016733","name":"item 2","amount":{"offset":100,"value":4000},"quantity":9,"sale_amount":{"offset":100,"value":2000}}],"subtotal":36400,"tax":{"value":500,"description":"tax description"},"shipping":{"value":900,"description":"shipping description"},"discount":{"value":1000,"description":"discount description","program_name":"discount program name"}},"payment_settings":{"type":"digital-goods","payment_link":"https://foo.bar","pix_config":{"key":"pix-key","key_type":"EMAIL","merchant_name":"merchant name","code":"pix-code"}}}}`), + ResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 200, + RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"template","template":{"name":"revive_issue","language":{"policy":"deterministic","code":"en"},"components":[{"type":"body","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]},{"type":"button","sub_type":"order_details","index":0,"parameters":[{"type":"action","action":{"order_details":{"reference_id":"220788123125","type":"digital-goods","payment_type":"br","payment_settings":[{"type":"payment_link","payment_link":{"uri":"https://foo.bar"}},{"type":"pix_dynamic_code","pix_dynamic_code":{"code":"pix-code","merchant_name":"merchant name","key":"pix-key","key_type":"EMAIL"}}],"currency":"BRL","total_amount":{"value":18200,"offset":100},"order":{"status":"pending","catalog_id":"c4t4l0g-1D","items":[{"retailer_id":"789236789","name":"item 1","quantity":2,"amount":{"value":200,"offset":100}},{"retailer_id":"59016733","name":"item 2","quantity":9,"amount":{"value":4000,"offset":100},"sale_amount":{"value":2000,"offset":100}}],"subtotal":{"value":36400,"offset":100},"tax":{"value":500,"offset":100,"description":"tax description"},"shipping":{"value":900,"offset":100,"description":"shipping description"},"discount":{"value":1000,"offset":100,"description":"discount description","discount_program_name":"discount program name"}}}}}]}]}}`, + SendPrep: setSendURL}, + {Label: "Message Template - Buttons", + Text: "templated message", + URN: "whatsapp:250788123123", + Status: "W", ExternalID: "157b5e14568e8", + Metadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "language": "eng", "variables": ["Chef", "tomorrow"]},"buttons":[{"sub_type":"url","parameters":[{"type":"text","text":"first param"}]}]}`), + ResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 200, + RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"template","template":{"name":"revive_issue","language":{"policy":"deterministic","code":"en"},"components":[{"type":"body","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]},{"type":"button","sub_type":"url","index":0,"parameters":[{"type":"text","text":"first param"}]}]}}`, SendPrep: setSendURL}, {Label: "Mesage without text and with quick replies and attachments should not be sent", Text: "", URN: "whatsapp:250788123123", diff --git a/handlers/test.go b/handlers/test.go index 58d0c7b98..ac1722473 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -316,7 +316,7 @@ func RunChannelSendTestCases(t *testing.T, channel courier.Channel, handler cour } if (len(testCase.Responses)) != 0 { - require.Equal(mockRRCount, len(testCase.Responses), "Probably MockRequest or MockResponse from testcase is not equal that processed in handler", testCase.Responses) + require.Equal(len(testCase.Responses), mockRRCount, "Probably MockRequest or MockResponse from testcase is not equal that processed in handler", testCase.Responses) } if testCase.Headers != nil { diff --git a/msg.go b/msg.go index fe3969328..2200a21f3 100644 --- a/msg.go +++ b/msg.go @@ -135,6 +135,17 @@ type Msg interface { CTAMessage() *CTAMessage FlowMessage() *FlowMessage OrderDetailsMessage() *OrderDetailsMessage + Buttons() []ButtonComponent +} + +type ButtonComponent struct { + SubType string `json:"sub_type"` + Parameters []ButtonParam `json:"parameters"` +} + +type ButtonParam struct { + Type string `json:"type"` + Text string `json:"text"` } type ListMessage struct { diff --git a/test.go b/test.go index 2081723e2..c9a09ce38 100644 --- a/test.go +++ b/test.go @@ -845,104 +845,143 @@ func (m *mockMsg) OrderDetailsMessage() *OrderDetailsMessage { return nil } - if interactionType, ok := metadata["interaction_type"].(string); ok && interactionType == "order_details" { - if orderDetailsMessageData, ok := metadata["order_details_message"].(map[string]interface{}); ok { - orderDetailsMessage := &OrderDetailsMessage{} - if referenceID, ok := orderDetailsMessageData["reference_id"].(string); ok { - orderDetailsMessage.ReferenceID = referenceID + if orderDetailsMessageData, ok := metadata["order_details_message"].(map[string]interface{}); ok { + orderDetailsMessage := &OrderDetailsMessage{} + if referenceID, ok := orderDetailsMessageData["reference_id"].(string); ok { + orderDetailsMessage.ReferenceID = referenceID + } + if paymentSettings, ok := orderDetailsMessageData["payment_settings"].(map[string]interface{}); ok { + orderDetailsMessage.PaymentSettings = OrderPaymentSettings{} + if payment_type, ok := paymentSettings["type"].(string); ok { + orderDetailsMessage.PaymentSettings.Type = payment_type + } + if payment_link, ok := paymentSettings["payment_link"].(string); ok { + orderDetailsMessage.PaymentSettings.PaymentLink = payment_link } - if paymentSettings, ok := orderDetailsMessageData["payment_settings"].(map[string]interface{}); ok { - orderDetailsMessage.PaymentSettings = OrderPaymentSettings{} - if payment_type, ok := paymentSettings["type"].(string); ok { - orderDetailsMessage.PaymentSettings.Type = payment_type + if pix_config, ok := paymentSettings["pix_config"].(map[string]interface{}); ok { + orderDetailsMessage.PaymentSettings.PixConfig = OrderPixConfig{} + if pix_config_key, ok := pix_config["key"].(string); ok { + orderDetailsMessage.PaymentSettings.PixConfig.Key = pix_config_key } - if payment_link, ok := paymentSettings["payment_link"].(string); ok { - orderDetailsMessage.PaymentSettings.PaymentLink = payment_link + if pix_config_key_type, ok := pix_config["key_type"].(string); ok { + orderDetailsMessage.PaymentSettings.PixConfig.KeyType = pix_config_key_type } - if pix_config, ok := paymentSettings["pix_config"].(map[string]interface{}); ok { - orderDetailsMessage.PaymentSettings.PixConfig = OrderPixConfig{} - if pix_config_key, ok := pix_config["key"].(string); ok { - orderDetailsMessage.PaymentSettings.PixConfig.Key = pix_config_key - } - if pix_config_key_type, ok := pix_config["key_type"].(string); ok { - orderDetailsMessage.PaymentSettings.PixConfig.KeyType = pix_config_key_type - } - if pix_config_merchant_name, ok := pix_config["merchant_name"].(string); ok { - orderDetailsMessage.PaymentSettings.PixConfig.MerchantName = pix_config_merchant_name - } - if pix_config_code, ok := pix_config["code"].(string); ok { - orderDetailsMessage.PaymentSettings.PixConfig.Code = pix_config_code - } + if pix_config_merchant_name, ok := pix_config["merchant_name"].(string); ok { + orderDetailsMessage.PaymentSettings.PixConfig.MerchantName = pix_config_merchant_name + } + if pix_config_code, ok := pix_config["code"].(string); ok { + orderDetailsMessage.PaymentSettings.PixConfig.Code = pix_config_code } } - if totalAmount, ok := orderDetailsMessageData["total_amount"].(float64); ok { - orderDetailsMessage.TotalAmount = int(totalAmount) - } - if orderData, ok := orderDetailsMessageData["order"].(map[string]interface{}); ok { - orderDetailsMessage.Order = Order{} - if itemsData, ok := orderData["items"].([]interface{}); ok { - orderDetailsMessage.Order.Items = make([]OrderItem, len(itemsData)) - for i, item := range itemsData { - if itemMap, ok := item.(map[string]interface{}); ok { - itemAmount := itemMap["amount"].(map[string]interface{}) - item := OrderItem{ - RetailerID: itemMap["retailer_id"].(string), - Name: itemMap["name"].(string), - Quantity: int(itemMap["quantity"].(float64)), - Amount: OrderAmountWithOffset{ - Value: int(itemAmount["value"].(float64)), - Offset: int(itemAmount["offset"].(float64)), - }, - } + } + if totalAmount, ok := orderDetailsMessageData["total_amount"].(float64); ok { + orderDetailsMessage.TotalAmount = int(totalAmount) + } + if orderData, ok := orderDetailsMessageData["order"].(map[string]interface{}); ok { + orderDetailsMessage.Order = Order{} + if itemsData, ok := orderData["items"].([]interface{}); ok { + orderDetailsMessage.Order.Items = make([]OrderItem, len(itemsData)) + for i, item := range itemsData { + if itemMap, ok := item.(map[string]interface{}); ok { + itemAmount := itemMap["amount"].(map[string]interface{}) + item := OrderItem{ + RetailerID: itemMap["retailer_id"].(string), + Name: itemMap["name"].(string), + Quantity: int(itemMap["quantity"].(float64)), + Amount: OrderAmountWithOffset{ + Value: int(itemAmount["value"].(float64)), + Offset: int(itemAmount["offset"].(float64)), + }, + } - if itemMap["sale_amount"] != nil { - saleAmount := itemMap["sale_amount"].(map[string]interface{}) - item.SaleAmount = &OrderAmountWithOffset{ - Value: int(saleAmount["value"].(float64)), - Offset: int(saleAmount["offset"].(float64)), - } + if itemMap["sale_amount"] != nil { + saleAmount := itemMap["sale_amount"].(map[string]interface{}) + item.SaleAmount = &OrderAmountWithOffset{ + Value: int(saleAmount["value"].(float64)), + Offset: int(saleAmount["offset"].(float64)), } - - orderDetailsMessage.Order.Items[i] = item } + + orderDetailsMessage.Order.Items[i] = item } } - if subtotal, ok := orderData["subtotal"].(float64); ok { - orderDetailsMessage.Order.Subtotal = int(subtotal) + } + if subtotal, ok := orderData["subtotal"].(float64); ok { + orderDetailsMessage.Order.Subtotal = int(subtotal) + } + if taxData, ok := orderData["tax"].(map[string]interface{}); ok { + orderDetailsMessage.Order.Tax = OrderAmountWithDescription{} + if value, ok := taxData["value"].(float64); ok { + orderDetailsMessage.Order.Tax.Value = int(value) } - if taxData, ok := orderData["tax"].(map[string]interface{}); ok { - orderDetailsMessage.Order.Tax = OrderAmountWithDescription{} - if value, ok := taxData["value"].(float64); ok { - orderDetailsMessage.Order.Tax.Value = int(value) - } - if description, ok := taxData["description"].(string); ok { - orderDetailsMessage.Order.Tax.Description = description - } + if description, ok := taxData["description"].(string); ok { + orderDetailsMessage.Order.Tax.Description = description } - if shippingData, ok := orderData["shipping"].(map[string]interface{}); ok { - orderDetailsMessage.Order.Shipping = OrderAmountWithDescription{} - if value, ok := shippingData["value"].(float64); ok { - orderDetailsMessage.Order.Shipping.Value = int(value) - } - if description, ok := shippingData["description"].(string); ok { - orderDetailsMessage.Order.Shipping.Description = description - } + } + if shippingData, ok := orderData["shipping"].(map[string]interface{}); ok { + orderDetailsMessage.Order.Shipping = OrderAmountWithDescription{} + if value, ok := shippingData["value"].(float64); ok { + orderDetailsMessage.Order.Shipping.Value = int(value) } - if discountData, ok := orderData["discount"].(map[string]interface{}); ok { - orderDetailsMessage.Order.Discount = OrderDiscount{} - if value, ok := discountData["value"].(float64); ok { - orderDetailsMessage.Order.Discount.Value = int(value) - } - if description, ok := discountData["description"].(string); ok { - orderDetailsMessage.Order.Discount.Description = description - } - if programName, ok := discountData["program_name"].(string); ok { - orderDetailsMessage.Order.Discount.ProgramName = programName - } + if description, ok := shippingData["description"].(string); ok { + orderDetailsMessage.Order.Shipping.Description = description + } + } + if discountData, ok := orderData["discount"].(map[string]interface{}); ok { + orderDetailsMessage.Order.Discount = OrderDiscount{} + if value, ok := discountData["value"].(float64); ok { + orderDetailsMessage.Order.Discount.Value = int(value) + } + if description, ok := discountData["description"].(string); ok { + orderDetailsMessage.Order.Discount.Description = description + } + if programName, ok := discountData["program_name"].(string); ok { + orderDetailsMessage.Order.Discount.ProgramName = programName + } + } + } + return orderDetailsMessage + } + + return nil +} + +func (m *mockMsg) Buttons() []ButtonComponent { + if m.metadata == nil { + return nil + } + + var metadata map[string]interface{} + err := json.Unmarshal(m.metadata, &metadata) + if err != nil { + return nil + } + + if metadata == nil { + return nil + } + + if buttonsData, ok := metadata["buttons"].([]interface{}); ok { + buttons := make([]ButtonComponent, len(buttonsData)) + for i, button := range buttonsData { + buttonMap := button.(map[string]interface{}) + buttons[i] = ButtonComponent{ + SubType: buttonMap["sub_type"].(string), + Parameters: []ButtonParam{}, + } + + if buttonMap["parameters"] != nil { + parameters := buttonMap["parameters"].([]interface{}) + for _, parameter := range parameters { + parameterMap := parameter.(map[string]interface{}) + buttons[i].Parameters = append(buttons[i].Parameters, ButtonParam{ + Type: parameterMap["type"].(string), + Text: parameterMap["text"].(string), + }) } } - return orderDetailsMessage } + return buttons } return nil