How do I get the locale in conversationUpdate activity? - c#

I would like to implement a welcome message for every members added based on their locale. The code is as following:
if (message.Type == ActivityTypes.ConversationUpdate)
{
// some code...
if (message.Locale == "en-us")
{
var reply = message.CreateReply("Hello world");
}
else
{
// some code...
}
// some code...
}
Strangely, the locale is null even though I've set the locale both when I test it using bot emulator and BotFramework-WebChat. The locale property is working fine when messages is received.
Is there any way I could get the locale during conversationUpdate activity?
Thank you in advance!

One solution to get the language of the user before his 1st input is to use the backchannel feature of the Webchat. This allows us to push information to our bot in a hidden way, to provide locale or a custom id for example.
You will found this demo on GitHub account here.
In particular, here is the sample of integration of your webchat sending the event to the bot at the end with a postActivity, created from the samples on Microsoft's Webchat GitHub description:
<!DOCTYPE html>
<html>
<head>
<link href="https://cdn.botframework.com/botframework-webchat/latest/botchat.css" rel="stylesheet" />
</head>
<body>
<div id="bot" />
<script src="https://cdn.botframework.com/botframework-webchat/latest/botchat.js"></script>
<script>
// Get parameters from query
const params = BotChat.queryParams(location.search);
// Language definition
var chatLocale = params['locale'] || window.navigator.language;
// Connection settings
const botConnectionSettings = new BotChat.DirectLine({
domain: params['domain'],
secret: 'YOUR_SECRET',
webSocket: params['webSocket'] && params['webSocket'] === 'true' // defaults to true
});
// Webchat init
BotChat.App({
botConnection: botConnectionSettings,
user: { id: 'userid' },
bot: { id: 'botid' },
locale: chatLocale,
resize: 'detect'
}, document.getElementById('bot'));
// Send message to provide language of user
botConnectionSettings.postActivity({
type: 'event',
from: { id: 'userid' },
locale: chatLocale,
name: 'localeSelectionEvent',
value: chatLocale
}).subscribe(function (id) { console.log('event language "' + chatLocale + '" selection sent'); });
</script>
</body>
</html>
I added a console event to show the activity posted.
This event is then received by the bot's MessageController and treated:
[BotAuthentication]
public class MessagesController : ApiController
{
/// <summary>
/// POST: api/Messages
/// Receive a message from a user and reply to it
/// </summary>
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
// DEMO PURPOSE: echo all incoming activities
Activity reply = activity.CreateReply(Newtonsoft.Json.JsonConvert.SerializeObject(activity, Newtonsoft.Json.Formatting.None));
var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
connector.Conversations.SendToConversation(reply);
// Process each activity
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
// Webchat: getting an "event" activity for our js code
else if (activity.Type == ActivityTypes.Event && activity.ChannelId == "webchat")
{
var receivedEvent = activity.AsEventActivity();
if ("localeSelectionEvent".Equals(receivedEvent.Name, StringComparison.InvariantCultureIgnoreCase))
{
await EchoLocaleAsync(activity, activity.Locale);
}
}
// Sample for emulator, to debug locales
else if (activity.Type == ActivityTypes.ConversationUpdate && activity.ChannelId == "emulator")
{
foreach (var userAdded in activity.MembersAdded)
{
if (userAdded.Id == activity.From.Id)
{
await EchoLocaleAsync(activity, "fr-FR");
}
}
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
private async Task EchoLocaleAsync(Activity activity, string inputLocale)
{
Activity reply = activity.CreateReply($"User locale is {inputLocale}, you should use this language for further treatment");
var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
await connector.Conversations.SendToConversationAsync(reply);
}
}
Illustration de l'arrivée des messages :
For French speaking users, here is a more detailed answer on my company's blog

With DirectLineJs 0.13.0, a property has been added that allows to specify the start locale like this:
...
createDirectLine({
token: tokenResponse.token,
domain: `https://europe.directline.botframework.com/v3/directline`,
pollingInterval: 500,
conversationStartProperties: {
locale : "fr"
},
watermark: "-",
})

Related

Stripe Redirect After Event

I'm using .NET and Stripe to make a webshop, and I'm trying to figure out how to redirect a customer to a success page, once the charge has been successfull. However Stripe recently changed their API, and I've been unable to find any resources online explaining how to do this.
I've tried creating a webhook that listens to the charge.succeeded event, and I can get the event to trigger, but I'm unable to redirect the customer to any page from the webhook.
Another thing I've tried is in the checkout page where I've added method="post" to the form, and type="submit" and formmethod="post" to the button respectively, so that when the customer clicks "Pay," the customer is redirected through the post method of the checkout page, but I can't get the post method to run.
Checkout razor page:
<head>
<title>Checkout</title>
<script src="https://js.stripe.com/v3/"></script>
</head>
<--! This is where I've tried method="post", type="submit" and formmethod="post" -->
<form id="payment-form">
<div id="card-element">
<!-- Elements will create input elements here -->
</div>
<!-- We'll put the error messages in this element -->
<div id="card-errors" role="alert"></div>
<button id="submit">Pay</button>
</form>
#section scripts{
<script>
// Set your publishable key: remember to change this to your live publishable key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
var stripe = Stripe('{PUBLIC KEY}');
var elements = stripe.elements();
window.onload = function () {
// Set up Stripe.js and Elements to use in checkout form
var style = {
base: {
color: "#32325d",
}
};
var card = elements.create("card", { style: style });
card.mount("#card-element");
card.addEventListener('change', function (event) {
var displayError = document.getElementById('card-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
});
var form = document.getElementById('payment-form');
form.addEventListener('submit', function (ev) {
ev.preventDefault();
stripe.confirmCardPayment('#Model.ClientSecret', {
payment_method: {
card: card,
billing_details: {
name: '#Model.CustomerInformation.FirstName',
email: '#Model.CustomerInformation.Email',
address: {
city: '#Model.CustomerInformation.City',
line1: '#Model.CustomerInformation.Address1',
postal_code: '#Model.CustomerInformation.ZipCode'
}
}
}
}).then(function (result) {
if (result.error) {
// Show error to your customer (e.g., insufficient funds)
console.log(result.error.message);
} else {
// The payment has been processed!
if (result.paymentIntent.status === 'succeeded') {
// Show a success message to your customer
// There's a risk of the customer closing the window before callback
// execution. Set up a webhook or plugin to listen for the
// payment_intent.succeeded event that handles any business critical
// post-payment actions.
}
}
});
});
};
</script>
Webhook:
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Stripe;
namespace workspace.Controllers
{
[Route("api/[controller]")]
public class StripeWebHook : Controller
{
// If you are testing your webhook locally with the Stripe CLI you
// can find the endpoint's secret by running `stripe listen`
// Otherwise, find your endpoint's secret in your webhook settings in the Developer Dashboard
const string endpointSecret = "ENDPOINT SECRET";
[HttpPost]
public async Task<IActionResult> Index()
{
var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
try
{
var stripeEvent = EventUtility.ParseEvent(json);
// Handle the event
if (stripeEvent.Type == Events.PaymentIntentSucceeded)
{
var paymentIntent = stripeEvent.Data.Object as PaymentIntent;
Console.WriteLine("Successful!");
return Ok();
}
else if (stripeEvent.Type == Events.ChargeSucceeded)
{
Console.WriteLine("Successful!");
// This is where I've tried return RedirectToPage("/Index");
return Ok();
}
else if (stripeEvent.Type == Events.PaymentIntentCreated)
{
Console.WriteLine("Successful!");
return Ok();
}
else if (stripeEvent.Type == Events.PaymentMethodAttached)
{
var paymentMethod = stripeEvent.Data.Object as PaymentMethod;
Console.WriteLine("PaymentMethod was attached to a Customer!");
}
// ... handle other event types
else
{
// Unexpected event type
return BadRequest();
}
return Ok();
}
catch (StripeException e)
{
return BadRequest();
}
}
}
}
I think that we should iron out a few concepts here first. The webhook endpoint is a standalone API that lives in your system somewhere and reacts to Events that are posted to it from Stripe, such as the charge.succeeded Event.
Your Elements implementation in the browser is completely separate and can't respond to anything that your webhook endpoint can return in terms of HTTP codes (redirects and such).
To answer your core question directly, in the Javascript in the else block where it says that the payment was successfully processed, you can call [0]
location.href = "https://your-success-page.com"
... to send the user to a success page. The reason that the form won't submit is because the submit event of the form has been prevented with ev.preventDefault();.
This whole flow is documented in detail here [1][2].
Hope this helps!
[0] https://developer.mozilla.org/en-US/docs/Web/API/Window/location
[1] https://stripe.com/docs/payments/accept-a-payment
[2] https://stripe.com/docs/webhooks

BotFramework V4: how to send an event from the bot and catch it in react WebChat?

I send an event named "locationRequest" from my bot (.NET SDK)
Activity activity = new Activity
{
Type = ActivityTypes.Event,
Name = "locationRequest"
};
await stepContext.Context.SendActivityAsync(activity, cancellationToken);
I want to catch this event in the WebChat client application, based on the minizable-web-chat coded in React, and set a boolean locationRequested to True:
const store = createStore({}, ({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
}
});
}
else if(action.name == 'locationRequest'){
this.setState(() => ({
locationRequested: true
}));
}
return next(action);
});
I am unable to catch this event, any idea please ?
You're going down the right track. Basically, you can monitor for 'DIRECT_LINE/INCOMING_ACTIVITY' events and then check if the incoming activity has the appropriate name. For more details, take a look at the code snippet below and the Incoming Event Web Chat sample.
const store = createStore({}, ({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
}
});
}
else if(action.type === 'DIRECT_LINE/INCOMING_ACTIVITY'){
if (action.payload.activity.name === 'locationRequest') {
this.setState(() => ({
locationRequested: true
}));
}
}
return next(action);
});
You are sending an activity from your bot, which means you can try to catch the activity and then you can check if the name of the activity is "locationRequest", and then you change the value of the locationRequested variable.
It would look something like this:
// We are adding a new middleware to customize the behavior of DIRECT_LINE/INCOMING_ACTIVITY.
const store = window.WebChat.createStore(
{},
({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
if (action.payload.activity.name == "locationRequest") {
this.setState(() => ({ locationRequested: true }));
}
}
return next(action);
}
);
Hope this helps! Btw. I based my answer on this sample

PayPal express checkout: checking payment can be performed on button click

I'm using PayPal express checkout (checkout.js V4.0.0) with asp.net mvc to allow a user to pay for data transactions. What I need to do when the express checkout button is clicked is perform some checks on the database and confirm that PayPal can proceed (this is also time related, as the database could be in a locked processing state).
I've setup the Advanced Server Integration and I then call the create-payment controller from the payment section in paypal.Button.render, but this expects a json object with a PaymentID element to be returned. At what point am I able to perform these checks on server side and abort from the paypal process if PayPal can't continue? If a check fails, the server side also needs to return an appropriate error page or message to be displayed.
This is the paypal button code:
<script src="https://www.paypalobjects.com/api/checkout.js"></script>
<script>
paypal.Button.render({
env: 'sandbox',
payment: function (resolve, reject) {
var CREATE_PAYMENT_URL = '#Url.Action("PayTransactions","Pending")';
paypal.request.post(CREATE_PAYMENT_URL)
.then(function (data) { resolve(data.paymentID); })
.catch(function (err) { reject(err); });
},
onAuthorize: function(data) {
var EXECUTE_PAYMENT_URL = 'https://my-store.com/paypal/execute-payment';
paypal.request.post(EXECUTE_PAYMENT_URL,
{
paymentID: data.paymentID,
payerID: data.payerID
})
.then(function(data) { /* Go to a success page */ })
.catch(function (err) { /* Go to an error page */ });
},
onCancel: function (data, actions) {
return actions.redirect();
},
onError: function (err) {
// Show an error page here, when an error occurs
}
}, '#paypal-button');
</script>
which at the payment section calls this:
public async Task<string> PayTransactions()
{
// check if payment is still necessary or end of month is running
var condition = await CheckDatabaseIsUsable();
switch (condition)
{
case 1:
ModelState.AddModelError("error", "some error message");
return RedirectToAction("Index", "Pending");
case 2:
ModelState.AddModelError("error", "some other error");
return RedirectToAction("Index", "Pending");
}
var paypalPayment = FormPayPalPaymentObject();
return JsonConvert.SerializeObject(new { paymentID = paypalPayment.PaymentId });
}
The problem is that I am now mixing the ActionResult and json string return types.
You can return json also for the redirection responses and control with javascript when it is a redirection or and ok response.
Server side:
return JsonConvert.SerializeObject(new { redirect= Url.Action("Index", "Pending") });
Javascript:
paypal.request.post(CREATE_PAYMENT_URL)
.then(function (data) {
if(data.redirect)
{
//cancel the flow and redirect if needed
window.location.href = data.redirect;
}else{
resolve(data.paymentID);
}
})
.catch(function (err) { reject(err); });
},
Using an IActionResult object as the return value for the PayTransactions is preferable
public async Task<IActionResult> PayTransactions()
{
...
return Json(new { paymentID = paypalPayment.PaymentId });
}
Also consider that the modelstate errors you are adding are useless because of the redirection.
You can call reject(error) to cancel the payment.

Facebook share on a Facebook Page timeline, on behalf of the user

On a mobile app, I want to have a Share button that posts an image on behalf of the user, but instead of posting it on this user's timeline, it must be posted on a specific Facebook Page timeline.
By using the Share dialog, it seems there is no way to configure the target of the post (in this case, the Facebook Page). Or at least I couldn't find it.
How would you do it?
Note: the app is made in Unity/C#, although I don't think this would matter much
Try this code :)
<div id="fb-root"></div>
<script>
window.fbAsyncInit = function() {
FB.init({
appId : '**appId**',
channelUrl : '//local.facebook-test/channel.html',
status : true,
xfbml : true,
oauth : true
});
FB.login(function(response)
{
if (response.authResponse)
{
var opts = {
message : '**message**',
access_token: '**PageAccessToken**',
name : '**name**',
link : 'https://developers.facebook.com/docs/reference/dialogs/',
description : '**description**',
picture : 'http://url/to/pic.jpg'
};
FB.api('/me/feed', 'post', opts, function(response)
{
if (!response || response.error)
{
console.log(response.error);
alert('Posting error occured');
}else{
alert('Success - Post ID: ' + response.id);
}
});
} else {
alert('Not logged in');
}
}, { scope: 'manage_pages, publish_actions, user_photos' });
};
(function (d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) { return; }
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>

(Unable to) Get started with the Facebook C# SDK

My goal is to use the FB login button so that FB users can log into my ASP.NET MVC 3 website. It seems that things have changed recently with the Facebook C# SDK and all the old examples will not work with the new version. I've tried for a day to get them to work... I'm working off of the tutorial Getting Started with the Facebook C# SDK for ASP.NET
Currently when I browse to http://localhost:8033/ it seems to automatically log me in (even after a fresh restart of Chrome) because it shows "my-name uses my-app-name" and shows my picture. I expected it to instead show a FB login button. And when I go to http://localhost:8033/Home/About I get an error that Session["AccessToken"] is null (which makes sense because it's clearly not getting set).
Here's what I have:
HomeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Facebook;
namespace FacebookTest.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult About()
{
var accessToken = Session["AccessToken"].ToString();
var client = new FacebookClient(accessToken);
dynamic result = client.Get("me", new { fields = "name,id" });
string name = result.name;
string id = result.id;
ViewBag.Message = "Hello id: " + id;
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult FacebookLogin(HttpContext context)
{
var accessToken = context.Request["accessToken"];
context.Session["AccessToken"] = accessToken;
return RedirectToAction("About");
}
}
}
Index.cshtml
#{
ViewBag.Title = "Home Page";
}
<h2>#ViewBag.Message</h2>
<p>
To learn more about ASP.NET MVC visit http://asp.net/mvc.
</p>
<div id="fb-root"></div>
<script>
window.fbAsyncInit = function () {
FB.init({
//appId: 'YOUR_APP_ID', // App ID
appId: '<MY-NUMBER-REMOVED>', // App ID
status: true, // check login status
cookie: true, // enable cookies to allow the server to access the session
xfbml: true // parse XFBML
});
// Additional initialization code here
FB.Event.subscribe('auth.authResponseChange', function (response) {
if (response.status === 'connected') {
// the user is logged in and has authenticated your
// app, and response.authResponse supplies
// the user's ID, a valid access token, a signed
// request, and the time the access token
// and signed request each expire
var uid = response.authResponse.userID;
var accessToken = response.authResponse.accessToken;
// TODO: Handle the access token
// Do a post to the server to finish the logon
// This is a form post since we don't want to use AJAX
var form = document.createElement("form");
form.setAttribute("method", 'post');
//form.setAttribute("action", '/FacebookLogin.ashx');
form.setAttribute("action", '/Home/FacebookLogin');
var field = document.createElement("input");
field.setAttribute("type", "hidden");
field.setAttribute("name", 'accessToken');
field.setAttribute("value", accessToken);
form.appendChild(field);
document.body.appendChild(form);
form.submit();
} else if (response.status === 'not_authorized') {
// the user is logged in to Facebook,
// but has not authenticated your app
} else {
// the user isn't logged in to Facebook.
}
});
};
// Load the SDK Asynchronously
(function (d) {
var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
if (d.getElementById(id)) { return; }
js = d.createElement('script'); js.id = id; js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
ref.parentNode.insertBefore(js, ref);
} (document));
</script>
<div class="fb-login-button" data-show-faces="true" data-width="400" data-max-rows="1"></div>
About.cshtml
#{
ViewBag.Title = "About Us";
}
<h2>About</h2>
<p>
#ViewBag.Message
</p>
Can you tell me how to fix this so that a FB login button is displayed, and when clicked it asks the users to do a FB authentication, sends them back, and then my app recognizes them as a logged in user?
As for the Login button, if you are logged in to Facebook prior to visiting your app you will see the faces instead of the login button, the only way to get the Login button back is to go to facebook.com and do a logout or possibly do a facebook logout using the C# SDK. Depending on your requirements this may or may not be what you wanted. There is a bit about Re-Authentication in the SDK documentation if that is what you really want.
I've tweaked your app by removing the submit() and replaced it with an ajax post, The FacebookLogin action was changed and I added some error handling on the About action. Your original app will work but it will automatically redirect to About if you are logged in to Facebook.
Update Added a login link which does not use Javascript, insert appid and appsecret and adjust portnumber accordingly. This was adapted from the server side login sample found here which is far prettier than this code :)
Note The state value being passed in the server side flow should be a unqiue value that you should validate in the ConnectResponse() method, i.e. generate a value in FacebookLoginNoJs and make sure it's the same in ConnectResponse to prevent cross site request forgery
HomeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Facebook;
namespace FacebookTest.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult About()
{
ViewBag.Message = "Please log in first";
if (Session["AccessToken"] != null)
{
var accessToken = Session["AccessToken"].ToString();
var client = new FacebookClient(accessToken);
try
{
dynamic result = client.Get("me", new { fields = "name,id" });
string name = result.name;
string id = result.id;
ViewBag.Message = "Hello id: " + id + " aka " + name;
}
catch (FacebookOAuthException x)
{
}
}
return View();
}
public void FacebookLogin(string uid, string accessToken)
{
var context = this.HttpContext;
context.Session["AccessToken"] = accessToken;
}
public ActionResult FacebookLoginNoJs()
{
return Redirect("https://www.facebook.com/dialog/oauth?client_id=MY-APPID-REMOVED&redirect_uri=http://localhost:45400/Home/ConnectResponse&state=secret");
}
public ActionResult ConnectResponse(string state, string code, string error, string error_reason, string error_description, string access_token, string expires)
{
if (string.IsNullOrEmpty(error))
{
try
{
var client = new FacebookClient();
dynamic result = client.Post("oauth/access_token",
new
{
client_id = "MY-APPID-REMOVED",
client_secret = "MY-APP-SECRET-REMOVED",
redirect_uri = "http://localhost:45400/Home/ConnectResponse",
code = code
});
Session["AccessToken"] = result.access_token;
if (result.ContainsKey("expires"))
Session["ExpiresIn"] = DateTime.Now.AddSeconds(result.expires);
}
catch
{
// handle errors
}
}
else
{
// Declined, check error
}
return RedirectToAction("Index");
}
}
}
Index.cshtml
#{
ViewBag.Title = "Home Page";
}
<h2>#ViewBag.Message</h2>
<p>
To learn more about ASP.NET MVC visit http://asp.net/mvc.
</p>
<div id="fb-root"></div>
<script>
window.fbAsyncInit = function () {
FB.init({
//appId: 'YOUR_APP_ID', // App ID
appId: 'MY-APPID-REMOVED', // App ID
status: true, // check login status
cookie: true, // enable cookies to allow the server to access the session
xfbml: true // parse XFBML
});
// Additional initialization code here
FB.Event.subscribe('auth.authResponseChange', function (response) {
if (response.status === 'connected') {
var uid = response.authResponse.userID;
var accessToken = response.authResponse.accessToken;
var url = '/Home/FacebookLogin';
$.post(url, { uid: uid, accessToken: accessToken }, function (data) {
});
} else if (response.status === 'not_authorized') {
// the user is logged in to Facebook,
// but has not authenticated your app
} else {
// the user isn't logged in to Facebook.
}
});
};
// Load the SDK Asynchronously
(function (d) {
var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
if (d.getElementById(id)) { return; }
js = d.createElement('script'); js.id = id; js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
ref.parentNode.insertBefore(js, ref);
} (document));
</script>
<div class="fb-login-button" data-show-faces="true" data-width="400" data-max-rows="1"></div>
#Html.ActionLink("The NoJs Login", "FacebookLoginNoJs", "Home")
About.cshtml
#{
ViewBag.Title = "About Us";
}
<h2>About</h2>
<p>
#ViewBag.Message
</p>

Categories

Resources