From 46f814af3fe14f89e29b044bd15e7681e3d04678 Mon Sep 17 00:00:00 2001 From: Saurabh Newatiya Date: Tue, 7 May 2024 20:32:58 +0530 Subject: [PATCH] SMS-6815: Adding support for templated and non-templated location messages --- CHANGELOG.md | 4 + README.md | 440 ++++++++++++++++++++++++++++++++++++++++++++++++++ baseclient.go | 2 +- messages.go | 9 ++ utils.go | 13 ++ utils_test.go | 229 +++++++++++++++++++++++++- 6 files changed, 693 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b6d76e..cae75e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## [7.49.0](https://github.com/plivo/plivo-go/tree/v7.49.0) (2024-05-07) +**Feature - Adding support for interactive whatsapp messages** +- Added new param 'interactive' to [send message API](https://www.plivo.com/docs/sms/api/message#send-a-message) to support interactive 'whatsapp' messages + ## [7.48.0](https://github.com/plivo/plivo-go/tree/v7.48.0) (2024-05-07) **Feature - Adding support for interactive whatsapp messages** - Added new param 'interactive' to [send message API](https://www.plivo.com/docs/sms/api/message#send-a-message) to support interactive 'whatsapp' messages diff --git a/README.md b/README.md index 47f2bcd..a63beb9 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,446 @@ func testPhloRunWithParams() { } ``` + +## WhatsApp Messaging +Plivo's WhatsApp API allows you to send different types of messages over WhatsApp, including templated messages, free form messages and interactive messages. Below are some examples on how to use the Plivo Go SDK to send these types of messages. + +### Templated Messages +Templated messages are a crucial to your WhatsApp messaging experience, as businesses can only initiate WhatsApp conversation with their customers using templated messages. + +WhatsApp templates support 4 components: `header` , `body`, `footer` and `button`. At the point of sending messages, the template object you see in the code acts as a way to pass the dynamic values within these components. `header` can accomodate `text` or `media` (images, video, documents) content. `body` can accomodate text content. `button` can support dynamic values in a `url` button or to specify a developer-defined payload which will be returned when the WhatsApp user clicks on the `quick_reply` button. `footer` cannot have any dynamic variables. + +Example: +```go +package main + +import ( + "fmt" + "github.com/plivo/plivo-go/v7" +) + +func main() { + client, err := plivo.NewClient("", "", &plivo.ClientOptions{}) + if err != nil { + fmt.Println("Error:", err) + return + } + + // Create a WhatsApp template + template, err := plivo.CreateWhatsappTemplate(`{ + "name": "sample_purchase_feedback", + "language": "en_US", + "components": [ + { + "type": "header", + "parameters": [ + { + "type": "media", + "media": "https://xyz.com/img.jpg" + } + ] + }, + { + "type": "body", + "parameters": [ + { + "type": "text", + "text": "Water Purifier" + } + ] + } + ] + }`) + if err != nil { + fmt.Println("Error creating template:", err) + return + } + + // Send a templated message + response, err := client.Messages.Create(plivo.MessageCreateParams{ + Src: "source_number", + Dst: "destination_number", + Type: "whatsapp", + Template: &template, + }) + if err != nil { + fmt.Println("Error sending message:", err) + return + } + + fmt.Printf("Response: %#v\n", response) +} +``` + +### Free Form Messages +Non-templated or Free Form WhatsApp messages can be sent as a reply to a user-initiated conversation (Service conversation) or if there is an existing ongoing conversation created previously by sending a templated WhatsApp message. + +#### Free Form Text Message +Example: +```go +package main + +import ( + "fmt" + "github.com/plivo/plivo-go/v7" +) + +func main() { + client, err := plivo.NewClient("", "", &plivo.ClientOptions{}) + if err != nil { + fmt.Println("Error:", err) + return + } + + // Send a free form message + response, err := client.Messages.Create(plivo.MessageCreateParams{ + Src: "source_number", + Dst: "destination_number", + Text: "Hello! How can I help you today?", + Type: "whatsapp", + }) + if err != nil { + fmt.Println("Error sending message:", err) + return + } + + fmt.Printf("Response: %#v\n", response) +} +``` + +#### Free Form Media Message +Example: +```go +package main + +import ( + "fmt" + "github.com/plivo/plivo-go/v7" +) + +func main() { + client, err := plivo.NewClient("", "", &plivo.ClientOptions{}) + if err != nil { + fmt.Print("Error", err.Error()) + return + } + response, err := client.Messages.Create(plivo.MessageCreateParams{ + Src:"source_number", + Dst:"destination_number", + Type:"whatsapp", + Text:"Hello, this is sample text", + MediaUrls:[]string{"https://sample-videos.com/img/Sample-png-image-1mb.png"}, + URL: "https://foo.com/whatsapp_status/", + }) + if err != nil { + fmt.Print("Error sending message:", err.Error()) + return + } + fmt.Printf("Response: %#v\n", response) +} +``` + +### Interactive Messages +This guide shows how to send non-templated interactive messages to recipients using Plivo’s APIs. + +#### Quick Reply Buttons +Quick reply buttons allow customers to quickly respond to your message with predefined options. + +Example: +```go +package main + +import ( + "fmt" + "github.com/plivo/plivo-go/v7" +) + +func main() { + client, err := plivo.NewClient("", "", &plivo.ClientOptions{}) + if err != nil { + fmt.Println("Error:", err) + return + } + + // Create quick reply buttons + interactive, err := plivo.CreateWhatsappInteractive(`{ + "type": "button", + "body": { + "text": "Would you like to proceed?" + }, + "action": { + "buttons": [ + { + "title": "Yes", + "id": "yes" + }, + { + "title": "No", + "id": "no" + } + ] + } + }`) + if err != nil { + fmt.Println("Error creating interactive buttons:", err) + return + } + + // Send interactive message with quick reply buttons + response, err := client.Messages.Create(plivo.MessageCreateParams{ + Src: "source_number", + Dst: "destination_number", + Type: "whatsapp", + Interactive: &interactive, + }) + if err != nil { + fmt.Println("Error sending interactive message:", err) + return + } + + fmt.Printf("Response: %#v\n", response) +} +``` + +#### Interactive Lists +Interactive lists allow you to present customers with a list of options. + +Example: +```go +package main + +import ( + "fmt" + "github.com/plivo/plivo-go/v7" +) + +func main() { + client, err := plivo.NewClient("", "", &plivo.ClientOptions{}) + if err != nil { + fmt.Println("Error:", err) + return + } + + // Create an interactive list + interactive, err := plivo.CreateWhatsappInteractive(`{ + "type": "list", + "header": { + "type": "text", + "text": "Select an option" + }, + "body": { + "text": "Choose from the following options:" + }, + "action": { + "sections": [ + { + "title": "Options", + "rows": [ + { + "id": "option1", + "title": "Option 1", + "description": "Description of option 1" + }, + { + "id": "option2", + "title": "Option 2", + "description": "Description of option 2" + } + ] + } + ] + } + }`) + if err != nil { + fmt.Println("Error creating interactive list:", err) + return + } + + // Send interactive message with list + response, err := client.Messages.Create(plivo.MessageCreateParams{ + Src: "source_number", + Dst: "destination_number", + Type: "whatsapp", + Interactive: &interactive, + }) + if err != nil { + fmt.Println("Error sending interactive message:", err) + return + } + + fmt.Printf("Response: %#v\n", response) +} +``` + +#### Interactive CTA URLs +CTA URL messages allow you to send links and call-to-action buttons. + +Example: +```go +package main + +import ( + "fmt" + "github.com/plivo/plivo-go/v7" +) + +func main() { + client, err := plivo.NewClient("", "", &plivo.ClientOptions{}) + if err != nil { + fmt.Println("Error:", err) + return + } + + // Create a CTA URL message + interactive, err := plivo.CreateWhatsappInteractive(`{ + "type": "cta_url", + "header": { + "type": "media", + "media": "https://example.com/image.jpg" + }, + "body": { + "text": "Check out this link!" + }, + "footer": { + "text": "Footer text" + }, + "action": { + "buttons": [ + { + "title": "Visit Website", + "url": "https://example.com" + } + ] + } + }`) + if err != nil { + fmt.Println("Error creating CTA URL:", err) + return + } + + // Send interactive message with CTA URL + response, err := client.Messages.Create(plivo.MessageCreateParams{ + Src: "source_number", + Dst: "destination_number", + Type: "whatsapp", + Interactive: &interactive, + }) + if err != nil { + fmt.Println("Error sending interactive message:", err) + return + } + + fmt.Printf("Response: %#v\n", response) +} +``` + +### Location Messages +This guide shows how to send templated and non-templated location messages to recipients using Plivo’s APIs. + +#### Templated Location Messages +Example: +```go +package main + +import ( + "fmt" + "github.com/plivo/plivo-go/v7" +) + +func main() { + client, err := plivo.NewClient("", "", &plivo.ClientOptions{}) + if err != nil { + fmt.Println("Error:", err) + return + } + + // Create a WhatsApp template + template, err := plivo.CreateWhatsappTemplate(`{ + "name": "plivo_order_pickup", + "language": "en_US", + "components": [ + { + "type": "header", + "parameters": [ + { + "type": "location", + "location": { + "latitude": "37.483307", + "longitude": "122.148981", + "name": "Pablo Morales", + "address": "1 Hacker Way, Menlo Park, CA 94025" + } + } + ] + } + ] + }`) + if err != nil { + fmt.Println("Error creating template:", err) + return + } + + // Send a templated message + response, err := client.Messages.Create(plivo.MessageCreateParams{ + Src: "source_number", + Dst: "destination_number", + Type: "whatsapp", + Template: &template, + }) + if err != nil { + fmt.Println("Error sending message:", err) + return + } + + fmt.Printf("Response: %#v\n", response) +} +``` + +#### Non-Templated Location Messages +Example: +```go +package main + +import ( + "fmt" + "github.com/plivo/plivo-go/v7" +) + +func main() { + client, err := plivo.NewClient("", "", &plivo.ClientOptions{}) + if err != nil { + fmt.Println("Error:", err) + return + } + + // Create a WhatsApp location object + location, err := plivo.CreateWhatsappLocation(`{ + "latitude": "37.483307", + "longitude": "122.148981", + "name": "Pablo Morales", + "address": "1 Hacker Way, Menlo Park, CA 94025" + }`) + if err != nil { + fmt.Println("Error creating location:", err) + return + } + + // Send a templated message + response, err := client.Messages.Create(plivo.MessageCreateParams{ + Src: "source_number", + Dst: "destination_number", + Type: "whatsapp", + Location: &location, + }) + if err != nil { + fmt.Println("Error sending message:", err) + return + } + + fmt.Printf("Response: %#v\n", response) +} +``` + ### More examples Refer to the [Plivo API Reference](https://www.plivo.com/docs/sms/api/overview/) for more examples. diff --git a/baseclient.go b/baseclient.go index 8111ea3..c321ddf 100644 --- a/baseclient.go +++ b/baseclient.go @@ -13,7 +13,7 @@ import ( "github.com/google/go-querystring/query" ) -const sdkVersion = "7.48.0" +const sdkVersion = "7.49.0" const lookupBaseUrl = "lookup.plivo.com" diff --git a/messages.go b/messages.go index b473f27..2b51ec0 100644 --- a/messages.go +++ b/messages.go @@ -27,6 +27,7 @@ type MessageCreateParams struct { MessageExpiry int `json:"message_expiry,omitempty" url:"message_expiry,omitempty"` Template *Template `json:"template,omitempty" url:"template,omitempty"` Interactive *Interactive `json:"interactive,omitempty" url:"interactive,omitempty"` + Location *Location `json:"location,omitempty" url:"location,omitempty"` DLTEntityID string `json:"dlt_entity_id,omitempty" url:"dlt_entity_id,omitempty"` DLTTemplateID string `json:"dlt_template_id,omitempty" url:"dlt_template_id,omitempty"` DLTTemplateCategory string `json:"dlt_template_category,omitempty" url:"dlt_template_category,omitempty"` @@ -140,6 +141,14 @@ type Parameter struct { Payload string `mapstructure:"payload" json:"payload,omitempty"` Currency *Currency `mapstructure:"currency" json:"currency,omitempty"` DateTime *DateTime `mapstructure:"date_time" json:"date_time,omitempty"` + Location Location `mapstructure:"location" json:"location,omitempty"` +} + +type Location struct { + Longitude string `mapstructure:"longitude" json:"longitude,omitempty" validate:"required"` + Latitude string `mapstructure:"latitude" json:"latitude,omitempty" validate:"required"` + Name string `mapstructure:"name" json:"name,omitempty" validate:"required"` + Address string `mapstructure:"address" json:"address,omitempty" validate:"required"` } type Currency struct { diff --git a/utils.go b/utils.go index 441610d..477ffad 100644 --- a/utils.go +++ b/utils.go @@ -186,6 +186,19 @@ func CreateWhatsappInteractive(interactiveData string) (interactive Interactive, return } +func CreateWhatsappLocation(locationData string) (location Location, err error) { + err = json.Unmarshal([]byte(locationData), &location) + if err != nil { + return + } + validate := validator.New() + err = validate.Struct(location) + if err != nil { + return + } + return +} + func CreateWhatsappTemplate(templateData string) (template Template, err error) { err = json.Unmarshal([]byte(templateData), &template) if err != nil { diff --git a/utils_test.go b/utils_test.go index ac7bc7c..0a2a843 100644 --- a/utils_test.go +++ b/utils_test.go @@ -1,7 +1,8 @@ package plivo -import "testing" import ( + "testing" + "github.com/stretchr/testify/assert" ) @@ -175,7 +176,7 @@ func TestValidateSignatureV3Pass4(t *testing.T) { ) } -func TestCreateWhatsappTemplatePass(t *testing.T){ +func TestCreateWhatsappTemplatePass(t *testing.T) { template_data := `{ "name": "plivo_verification", "language": "en_US", @@ -207,7 +208,7 @@ func TestCreateWhatsappTemplatePass(t *testing.T){ Language: "en_US", Components: []Component{ { - Type: "body", + Type: "body", Parameters: []Parameter{ { Type: "text", @@ -232,6 +233,228 @@ func TestCreateWhatsappTemplatePass(t *testing.T){ assert.Equal(t, template, templateCreated) } +func TestCreateWhatsappInteractiveList(t *testing.T) { + interactiveData := `{ + "type": "list", + "header": { + "type": "text", + "text": "Welcome to Plivo" + }, + "body": { + "text": "You can review the list of rewards we offer" + }, + "footer": { + "text": "Yours Truly" + }, + "action": { + "buttons": [{ + "title": "Click here", + "id": "bt1j1k2jkjk" + }], + "sections": [{ + "title": "SECTION_1_TITLE", + "rows": [{ + "id": "SECTION_1_ROW_1_ID", + "title": "SECTION_1_ROW_1_TITLE", + "description": "SECTION_1_ROW_1_DESCRIPTION" + }, { + "id": "SECTION_1_ROW_2_ID", + "title": "SECTION_1_ROW_2_TITLE", + "description": "SECTION_1_ROW_2_DESCRIPTION" + }] + }, { + "title": "SECTION_2_TITLE", + "rows": [{ + "id": "SECTION_2_ROW_1_ID", + "title": "SECTION_2_ROW_1_TITLE", + "description": "SECTION_2_ROW_1_DESCRIPTION" + }, { + "id": "SECTION_2_ROW_2_ID", + "title": "SECTION_2_ROW_2_TITLE", + "description": "SECTION_2_ROW_2_DESCRIPTION" + }] + }] + } + }` + txt := "Welcome to Plivo" + expectedInteractive := Interactive{ + Type: "list", + Header: &Header{ + Type: "text", + Text: &txt, + }, + Body: &Body{ + Text: "You can review the list of rewards we offer", + }, + Footer: &Footer{ + Text: "Yours Truly", + }, + Action: &Action{ + Button: []*Buttons{ + { + ID: "bt1j1k2jkjk", + Title: "Click here", + }, + }, + Section: []*Section{ + { + Title: "SECTION_1_TITLE", + Row: []*Row{ + { + ID: "SECTION_1_ROW_1_ID", + Title: "SECTION_1_ROW_1_TITLE", + Description: "SECTION_1_ROW_1_DESCRIPTION", + }, + { + ID: "SECTION_1_ROW_2_ID", + Title: "SECTION_1_ROW_2_TITLE", + Description: "SECTION_1_ROW_2_DESCRIPTION", + }, + }, + }, + { + Title: "SECTION_2_TITLE", + Row: []*Row{ + { + ID: "SECTION_2_ROW_1_ID", + Title: "SECTION_2_ROW_1_TITLE", + Description: "SECTION_2_ROW_1_DESCRIPTION", + }, + { + ID: "SECTION_2_ROW_2_ID", + Title: "SECTION_2_ROW_2_TITLE", + Description: "SECTION_2_ROW_2_DESCRIPTION", + }, + }, + }, + }, + }, + } + interactive, _ := CreateWhatsappInteractive(interactiveData) + assert.Equal(t, expectedInteractive, interactive) +} +func TestCreateWhatsappInteractiveReply(t *testing.T) { + interactiveData := `{ + "type": "reply", + "header": { + "type": "media", + "media": "https://media.geeksforgeeks.org/wp-content/uploads/20190712220639/ybearoutput-300x225.png" + }, + "body": { + "text": "Make your selection" + }, + "action": { + "buttons": [ + { + "title": "Click here", + "id": "bt1" + }, + { + "title": "Know More", + "id": "bt2" + }, + { + "title": "Request Callback", + "id": "bt3" + } + ] + } + }` + mediaLink := "https://media.geeksforgeeks.org/wp-content/uploads/20190712220639/ybearoutput-300x225.png" + expectedInteractive := Interactive{ + Type: "reply", + Header: &Header{ + Type: "media", + Media: &mediaLink, + }, + Body: &Body{ + Text: "Make your selection", + }, + Action: &Action{ + Button: []*Buttons{ + { + ID: "bt1", + Title: "Click here", + }, + { + ID: "bt2", + Title: "Know More", + }, + { + ID: "bt3", + Title: "Request Callback", + }, + }, + }, + } + + interactive, _ := CreateWhatsappInteractive(interactiveData) + assert.Equal(t, expectedInteractive, interactive) +} + +func TestCreateWhatsappInteractiveCTA(t *testing.T) { + interactiveData := `{ + "type": "cta_url", + "header": { + "type": "media", + "media": "https://media.geeksforgeeks.org/wp-content/uploads/20190712220639/ybearoutput-300x225.png" + }, + "body": { + "text": "Know More" + }, + "action": { + "buttons": [ + { + "title": "Click here", + "id": "bt1", + "cta_url": "https://plivo.com" + } + ] + } + }` + + mediaLink := "https://media.geeksforgeeks.org/wp-content/uploads/20190712220639/ybearoutput-300x225.png" + expectedInteractive := Interactive{ + Type: "cta_url", + Header: &Header{ + Type: "media", + Media: &mediaLink, + }, + Body: &Body{ + Text: "Know More", + }, + Action: &Action{ + Button: []*Buttons{ + { + ID: "bt1", + Title: "Click here", + CTAURL: "https://plivo.com", + }, + }, + }, + } + + interactive, err := CreateWhatsappInteractive(interactiveData) + assert.NoError(t, err) + assert.Equal(t, expectedInteractive, interactive) +} + +func TestCreateWhatsappLocationPass(t *testing.T) { + location_data := `{ + "latitude": "37.483307", + "longitude": "122.148981", + "name": "Pablo Morales", + "address": "1 Hacker Way, Menlo Park, CA 94025" + }` + location := Location{ + Latitude: "37.483307", + Longitude: "122.148981", + Name: "Pablo Morales", + Address: "1 Hacker Way, Menlo Park, CA 94025", + } + locaitonCreated, _ := CreateWhatsappLocation(location_data) + assert.Equal(t, location, locaitonCreated) +}