diff --git a/handlers/dialog360/handler_test.go b/handlers/dialog360/handler_test.go index 60e7bfa55..3262de092 100644 --- a/handlers/dialog360/handler_test.go +++ b/handlers/dialog360/handler_test.go @@ -425,7 +425,7 @@ var SendTestCasesD3C = []OutgoingTestCase{ MsgText: "templated message", MsgURN: "whatsapp:250788123123", MsgLocale: "eng", - MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "components": [{"type": "body", "params": [{"type":"text", "value":"Chef"}, {"type": "text" , "value": "tomorrow"}]}], "language": "en_US"}}`), + MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "components": [{"type": "body", "params": [{"type":"text", "name": "1", "value":"Chef"}, {"type": "text", "name": "2", "value": "tomorrow"}]}], "language": "en_US"}}`), MockResponses: map[string][]*httpx.MockResponse{ "https://waba-v2.360dialog.io/messages": { httpx.NewMockResponse(200, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), diff --git a/handlers/meta/whataspp_test.go b/handlers/meta/whataspp_test.go index 69f67e418..7b828fe9c 100644 --- a/handlers/meta/whataspp_test.go +++ b/handlers/meta/whataspp_test.go @@ -394,7 +394,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ MsgText: "templated message", MsgURN: "whatsapp:250788123123", MsgLocale: "eng", - MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "components": [{"type":"body", "params": [{"type":"text", "value":"Chef"}, {"type": "text" , "value": "tomorrow"}]}], "language": "en_US"}}`), + MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "external_id": "ext_id_revive_issue", "components": [{"type":"body", "params": [{"type":"text", "name": "1", "value":"Chef"}, {"type": "text" , "name": "2", "value": "tomorrow"}]}], "language": "en_US"}}`), MockResponses: map[string][]*httpx.MockResponse{ "*/12345_ID/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -410,7 +410,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ MsgText: "templated message", MsgURN: "whatsapp:250788123123", MsgLocale: "eng", - MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "components": [], "variables": [], "language": "en_US"}}`), + MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "external_id": "ext_id_revive_issue", "components": [], "variables": [], "language": "en_US"}}`), MockResponses: map[string][]*httpx.MockResponse{ "*/12345_ID/messages": { httpx.NewMockResponse(200, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -426,17 +426,19 @@ var whatsappOutgoingTests = []OutgoingTestCase{ MsgText: "templated message", MsgURN: "whatsapp:250788123123", MsgLocale: "eng", - MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" },"components": [ + MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "external_id": "ext_id_revive_issue", "components": [ { "type": "body", "name": "body", "params": [ { "type": "text", + "name": "1", "value": "Ryan Lewis" }, { "type": "text", + "name": "2", "value": "niño" } ] @@ -447,6 +449,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ "params": [ { "type": "text", + "name": "1", "value": "Sip" } ] @@ -457,6 +460,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ "params": [ { "type": "url", + "name": "1", "value": "id00231" } ] diff --git a/handlers/meta/whatsapp/templates.go b/handlers/meta/whatsapp/templates.go index 241339392..80ca99fce 100644 --- a/handlers/meta/whatsapp/templates.go +++ b/handlers/meta/whatsapp/templates.go @@ -15,11 +15,13 @@ type MsgTemplating struct { UUID string `json:"uuid" validate:"required"` } `json:"template" validate:"required,dive"` Namespace string `json:"namespace"` + ExternalID string `json:"external_id"` Components []struct { Type string `json:"type"` Name string `json:"name"` Params []struct { Type string `json:"type"` + Name string `json:"name"` Value string `json:"value"` } `json:"params"` } `json:"components"` diff --git a/handlers/twiml/handlers.go b/handlers/twiml/handlers.go index 55c3e1365..0b3e51533 100644 --- a/handlers/twiml/handlers.go +++ b/handlers/twiml/handlers.go @@ -11,6 +11,7 @@ import ( "crypto/sha1" _ "embed" "encoding/base64" + "encoding/json" "fmt" "log/slog" "net/http" @@ -22,9 +23,11 @@ import ( "github.com/buger/jsonparser" "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" + "github.com/nyaruka/courier/handlers/meta/whatsapp" "github.com/nyaruka/courier/utils" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/urns" + "github.com/pkg/errors" ) const ( @@ -230,34 +233,40 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, res *courier.Sen return err } - parts := handlers.SplitMsgByChannel(msg.Channel(), msg.Text(), maxMsgLength) - for i, part := range parts { + // do we have a template? + templating, err := whatsapp.GetTemplating(msg) + if err != nil { + return errors.Wrapf(err, "unable to decode template: %s for channel: %s", string(msg.Metadata()), msg.Channel().UUID()) + } + + if templating != nil && channel.IsScheme(urns.WhatsAppScheme) { + + if templating.ExternalID == "" { + return courier.ErrFailedWithReason("", "template missing contentSID") + } + // build our request form := url.Values{ - "To": []string{msg.URN().Path()}, - "Body": []string{part}, + "To": []string{fmt.Sprintf("%s:+%s", urns.WhatsAppScheme, msg.URN().Path())}, "StatusCallback": []string{callbackURL}, + "ContentSid": []string{templating.ExternalID}, + "From": []string{fmt.Sprintf("%s:%s", urns.WhatsAppScheme, channel.Address())}, } - // add any attachments to the first part - if i == 0 { - for _, a := range attachments { - form.Add("MediaUrl", a.URL) + contentVariables := make(map[string]string) + + for _, comp := range templating.Components { + if comp.Type == "body" { + for _, p := range comp.Params { + contentVariables[p.Name] = p.Value + } } } - // set our from, either as a messaging service or from our address - serviceSID := channel.StringConfigForKey(configMessagingServiceSID, "") - if serviceSID != "" { - form["MessagingServiceSid"] = []string{serviceSID} - } else { - form["From"] = []string{channel.Address()} - } + contentVariablesJson, _ := json.Marshal(contentVariables) - // for whatsapp channels, we have to prepend whatsapp to the To and From - if channel.IsScheme(urns.WhatsAppScheme) { - form["To"][0] = fmt.Sprintf("%s:+%s", urns.WhatsAppScheme, form["To"][0]) - form["From"][0] = fmt.Sprintf("%s:%s", urns.WhatsAppScheme, form["From"][0]) + if len(contentVariables) > 0 { + form["ContentVariables"] = []string{string(contentVariablesJson)} } // build our URL @@ -310,6 +319,88 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, res *courier.Sen res.AddExternalID(externalID) } + } else { + parts := handlers.SplitMsgByChannel(msg.Channel(), msg.Text(), maxMsgLength) + for i, part := range parts { + // build our request + form := url.Values{ + "To": []string{msg.URN().Path()}, + "Body": []string{part}, + "StatusCallback": []string{callbackURL}, + } + + // add any attachments to the first part + if i == 0 { + for _, a := range attachments { + form.Add("MediaUrl", a.URL) + } + } + + // set our from, either as a messaging service or from our address + serviceSID := channel.StringConfigForKey(configMessagingServiceSID, "") + if serviceSID != "" { + form["MessagingServiceSid"] = []string{serviceSID} + } else { + form["From"] = []string{channel.Address()} + } + + // for whatsapp channels, we have to prepend whatsapp to the To and From + if channel.IsScheme(urns.WhatsAppScheme) { + form["To"][0] = fmt.Sprintf("%s:+%s", urns.WhatsAppScheme, form["To"][0]) + form["From"][0] = fmt.Sprintf("%s:%s", urns.WhatsAppScheme, form["From"][0]) + } + + // build our URL + baseURL := h.baseURL(channel) + if baseURL == "" { + return courier.ErrChannelConfig + } + + sendURL, err := utils.AddURLPath(baseURL, "2010-04-01", "Accounts", accountSID, "Messages.json") + if err != nil { + return err + } + + req, err := http.NewRequest(http.MethodPost, sendURL, strings.NewReader(form.Encode())) + if err != nil { + return err + } + req.SetBasicAuth(accountSID, accountToken) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Accept", "application/json") + + resp, respBody, err := h.RequestHTTP(req, clog) + if err != nil || resp.StatusCode/100 == 5 { + return courier.ErrConnectionFailed + } + + // see if we can parse the error if we have one + if resp.StatusCode/100 != 2 && len(respBody) > 0 { + errorCode, _ := jsonparser.GetInt(respBody, "code") + if errorCode != 0 { + if errorCode == errorStopped { + return courier.ErrContactStopped + } + codeAsStr := strconv.Itoa(int(errorCode)) + errMsg, err := jsonparser.GetString(errorCodes, codeAsStr) + if err != nil { + errMsg = fmt.Sprintf("Service specific error: %s.", codeAsStr) + } + return courier.ErrFailedWithReason(codeAsStr, errMsg) + } + + return courier.ErrResponseStatus + } + + // grab the external id + externalID, err := jsonparser.GetString(respBody, "sid") + if err != nil { + clog.Error(courier.ErrorResponseValueMissing("sid")) + } else { + res.AddExternalID(externalID) + } + + } } return nil diff --git a/handlers/twiml/handlers_test.go b/handlers/twiml/handlers_test.go index 7f2e272ff..46e8139e7 100644 --- a/handlers/twiml/handlers_test.go +++ b/handlers/twiml/handlers_test.go @@ -2,6 +2,7 @@ package twiml import ( "context" + "encoding/json" "net/http" "net/url" "testing" @@ -1051,7 +1052,25 @@ var waSendTestCases = []OutgoingTestCase{ }, }, ExpectedRequests: []ExpectedRequest{{ - Form: url.Values{"Body": {"Simple Message ☺"}, "To": {"whatsapp:+250788383383"}, "From": {"whatsapp:+12065551212"}, "StatusCallback": {"https://localhost/c/t/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?id=10&action=callback"}}, + Form: url.Values{"Body": {"Simple Message ☺"}, "To": {"whatsapp:+250788383383"}, "From": {"whatsapp:+12065551212"}, "StatusCallback": {"https://localhost/c/sw/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?id=10&action=callback"}}, + Headers: map[string]string{"Authorization": "Basic YWNjb3VudFNJRDphdXRoVG9rZW4="}, + }}, + ExpectedExtIDs: []string{"1002"}, + }, + { + Label: "Template Send", + MsgText: "templated message", + MsgURN: "whatsapp:250788383383", + MsgLocale: "eng", + MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "external_id": "ext_id_revive_issue", "components": [{"type":"body", "params": [{"type":"text", "name": "1", "value":"Chef"}, {"type": "text" , "name": "2", "value": "tomorrow"}]}]}}`), + MockResponses: map[string][]*httpx.MockResponse{ + "http://example.com/sigware_api/2010-04-01/Accounts/accountSID/Messages.json": { + httpx.NewMockResponse(200, nil, []byte(`{ "sid": "1002" }`)), + }, + }, + + ExpectedRequests: []ExpectedRequest{{ + Form: url.Values{"To": {"whatsapp:+250788383383"}, "From": {"whatsapp:+12065551212"}, "StatusCallback": {"https://localhost/c/sw/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?id=10&action=callback"}, "ContentSid": {"ext_id_revive_issue"}, "ContentVariables": {"{\"1\":\"Chef\",\"2\":\"tomorrow\"}"}}, Headers: map[string]string{"Authorization": "Basic YWNjb3VudFNJRDphdXRoVG9rZW4="}, }}, ExpectedExtIDs: []string{"1002"}, @@ -1074,6 +1093,99 @@ var twaSendTestCases = []OutgoingTestCase{ }}, ExpectedExtIDs: []string{"1002"}, }, + { + Label: "Template Send", + MsgText: "templated message", + MsgURN: "whatsapp:250788383383", + MsgLocale: "eng", + MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "external_id": "ext_id_revive_issue", "components": [{"type":"body", "params": [{"type":"text", "name": "1", "value":"Chef"}, {"type": "text" , "name": "2", "value": "tomorrow"}]}]}}`), + MockResponses: map[string][]*httpx.MockResponse{ + "https://api.twilio.com/2010-04-01/Accounts/accountSID/Messages.json": { + httpx.NewMockResponse(200, nil, []byte(`{ "sid": "1002" }`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Form: url.Values{"To": {"whatsapp:+250788383383"}, "From": {"whatsapp:+12065551212"}, "StatusCallback": {"https://localhost/c/twa/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?id=10&action=callback"}, "ContentSid": {"ext_id_revive_issue"}, "ContentVariables": {"{\"1\":\"Chef\",\"2\":\"tomorrow\"}"}}, + Headers: map[string]string{"Authorization": "Basic YWNjb3VudFNJRDphdXRoVG9rZW4="}, + }}, + ExpectedExtIDs: []string{"1002"}, + }, + { + Label: "Template Send missing external ID", + MsgText: "templated message", + MsgURN: "whatsapp:250788383383", + MsgLocale: "eng", + MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "components": [{"type":"body", "params": [{"type":"text", "name": "1", "value":"Chef"}, {"type": "text" , "name": "2", "value": "tomorrow"}]}]}}`), + ExpectedError: courier.ErrFailedWithReason("", "template missing contentSID"), + }, + { + Label: "Error Code", + MsgText: "Error Code", + MsgURN: "whatsapp:250788383383", + MsgLocale: "eng", + MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "external_id": "ext_id_revive_issue", "components": [{"type":"body", "params": [{"type":"text", "name": "1", "value":"Chef"}, {"type": "text" , "name": "2", "value": "tomorrow"}]}]}}`), + MockResponses: map[string][]*httpx.MockResponse{ + "https://api.twilio.com/2010-04-01/Accounts/accountSID/Messages.json": { + httpx.NewMockResponse(400, nil, []byte(`{ "code": 1001 }`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Form: url.Values{"To": {"whatsapp:+250788383383"}, "From": {"whatsapp:+12065551212"}, "StatusCallback": {"https://localhost/c/twa/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?id=10&action=callback"}, "ContentSid": {"ext_id_revive_issue"}, "ContentVariables": {"{\"1\":\"Chef\",\"2\":\"tomorrow\"}"}}, + Headers: map[string]string{"Authorization": "Basic YWNjb3VudFNJRDphdXRoVG9rZW4="}, + }}, + ExpectedError: courier.ErrFailedWithReason("1001", "Service specific error: 1001."), + }, + { + Label: "Stopped Contact Code", + MsgText: "Stopped Contact", + MsgURN: "whatsapp:250788383383", + MsgLocale: "eng", + MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "external_id": "ext_id_revive_issue", "components": [{"type":"body", "params": [{"type":"text", "name": "1", "value":"Chef"}, {"type": "text" , "name": "2", "value": "tomorrow"}]}]}}`), + MockResponses: map[string][]*httpx.MockResponse{ + "https://api.twilio.com/2010-04-01/Accounts/accountSID/Messages.json": { + httpx.NewMockResponse(400, nil, []byte(`{ "code": 21610 }`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Form: url.Values{"To": {"whatsapp:+250788383383"}, "From": {"whatsapp:+12065551212"}, "StatusCallback": {"https://localhost/c/twa/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?id=10&action=callback"}, "ContentSid": {"ext_id_revive_issue"}, "ContentVariables": {"{\"1\":\"Chef\",\"2\":\"tomorrow\"}"}}, + Headers: map[string]string{"Authorization": "Basic YWNjb3VudFNJRDphdXRoVG9rZW4="}, + }}, + ExpectedError: courier.ErrContactStopped, + }, + { + Label: "No SID", + MsgText: "No SID", + MsgURN: "whatsapp:250788383383", + MsgLocale: "eng", + MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "external_id": "ext_id_revive_issue", "components": [{"type":"body", "params": [{"type":"text", "name": "1", "value":"Chef"}, {"type": "text" , "name": "2", "value": "tomorrow"}]}]}}`), + MockResponses: map[string][]*httpx.MockResponse{ + "https://api.twilio.com/2010-04-01/Accounts/accountSID/Messages.json": { + httpx.NewMockResponse(200, nil, []byte(`{ }`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Form: url.Values{"To": {"whatsapp:+250788383383"}, "From": {"whatsapp:+12065551212"}, "StatusCallback": {"https://localhost/c/twa/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?id=10&action=callback"}, "ContentSid": {"ext_id_revive_issue"}, "ContentVariables": {"{\"1\":\"Chef\",\"2\":\"tomorrow\"}"}}, + Headers: map[string]string{"Authorization": "Basic YWNjb3VudFNJRDphdXRoVG9rZW4="}, + }}, + ExpectedLogErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("sid")}, + }, + { + Label: "Error Sending", + MsgText: "Error Message", + MsgURN: "whatsapp:250788383383", + MsgLocale: "eng", + MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "external_id": "ext_id_revive_issue", "components": [{"type":"body", "params": [{"type":"text", "name": "1", "value":"Chef"}, {"type": "text" , "name": "2", "value": "tomorrow"}]}]}}`), + MockResponses: map[string][]*httpx.MockResponse{ + "https://api.twilio.com/2010-04-01/Accounts/accountSID/Messages.json": { + httpx.NewMockResponse(401, nil, []byte(`{ "error": "out of credits" }`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Form: url.Values{"To": {"whatsapp:+250788383383"}, "From": {"whatsapp:+12065551212"}, "StatusCallback": {"https://localhost/c/twa/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?id=10&action=callback"}, "ContentSid": {"ext_id_revive_issue"}, "ContentVariables": {"{\"1\":\"Chef\",\"2\":\"tomorrow\"}"}}, + Headers: map[string]string{"Authorization": "Basic YWNjb3VudFNJRDphdXRoVG9rZW4="}, + }}, + ExpectedError: courier.ErrResponseStatus, + }, } func TestOutgoing(t *testing.T) { @@ -1117,7 +1229,7 @@ func TestOutgoing(t *testing.T) { ) waChannel.SetScheme(urns.WhatsAppScheme) - RunOutgoingTestCases(t, waChannel, newTWIMLHandler("T", "Twilio Whatsapp", true), waSendTestCases, []string{httpx.BasicAuth("accountSID", "authToken")}, nil) + RunOutgoingTestCases(t, waChannel, newTWIMLHandler("SW", "SignalWire", true), waSendTestCases, []string{httpx.BasicAuth("accountSID", "authToken")}, nil) twaChannel := test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "TWA", "+12065551212", "US", map[string]any{ diff --git a/handlers/whatsapp_legacy/handler.go b/handlers/whatsapp_legacy/handler.go index f7b147b65..9e64472e1 100644 --- a/handlers/whatsapp_legacy/handler.go +++ b/handlers/whatsapp_legacy/handler.go @@ -16,6 +16,7 @@ import ( "github.com/gomodule/redigo/redis" "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" + "github.com/nyaruka/courier/handlers/meta/whatsapp" "github.com/nyaruka/courier/utils" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/i18n" @@ -697,7 +698,7 @@ func buildPayloads(msg courier.MsgOut, h *handler, clog *courier.ChannelLog) ([] } else { // do we have a template? - templating, err := h.getTemplating(msg) + templating, err := whatsapp.GetTemplating(msg) if err != nil { return nil, errors.Wrapf(err, "unable to decode template: %s for channel: %s", string(msg.Metadata()), msg.Channel().UUID()) } @@ -1080,45 +1081,6 @@ func (h *handler) checkWhatsAppContact(channel courier.Channel, baseURL string, } } -func (h *handler) getTemplating(msg courier.MsgOut) (*MsgTemplating, error) { - if len(msg.Metadata()) == 0 { - return nil, nil - } - - metadata := &struct { - Templating *MsgTemplating `json:"templating"` - }{} - if err := json.Unmarshal(msg.Metadata(), metadata); err != nil { - return nil, err - } - - if metadata.Templating == nil { - return nil, nil - } - - if err := utils.Validate(metadata.Templating); err != nil { - return nil, errors.Wrapf(err, "invalid templating definition") - } - - return metadata.Templating, nil -} - -type MsgTemplating struct { - Template struct { - Name string `json:"name" validate:"required"` - UUID string `json:"uuid" validate:"required"` - } `json:"template" validate:"required,dive"` - Namespace string `json:"namespace"` - Components []struct { - Type string `json:"type"` - Name string `json:"name"` - Params []struct { - Type string `json:"type"` - Value string `json:"value"` - } `json:"params"` - } `json:"components"` -} - func getSupportedLanguage(lc i18n.Locale) string { // look for exact match if lang := supportedLanguages[lc]; lang != "" { diff --git a/handlers/whatsapp_legacy/handler_test.go b/handlers/whatsapp_legacy/handler_test.go index dc5eea935..5ae4134fb 100644 --- a/handlers/whatsapp_legacy/handler_test.go +++ b/handlers/whatsapp_legacy/handler_test.go @@ -766,7 +766,7 @@ var defaultSendTestCases = []OutgoingTestCase{ MsgText: "templated message", MsgURN: "whatsapp:250788123123", MsgLocale: "eng", - MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "components": [{"type":"body", "params": [{"type":"text", "value":"Chef"}, {"type": "text" , "value": "tomorrow"}]}]}}`), + MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "external_id": "ext_id_revive_issue", "components": [{"type":"body", "params": [{"type":"text", "name": "1", "value":"Chef"}, {"type": "text" , "name": "2", "value": "tomorrow"}]}]}}`), MockResponses: map[string][]*httpx.MockResponse{ "*/v1/messages": { httpx.NewMockResponse(200, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)),