How to pass additional params to Stripe Custom Flow? - stripe-payments

Following Stripes official tutorial to create payment form with apple pay support: https://stripe.com/docs/payments/quickstart#collect-billing-address-details
But on thank you page, i need to know: user email and stripe customer id. How i can pass these parameters to thank you page?
My code for prepare.php:
$productID = $request['items'][0]['id'];
$customer = \Stripe\Customer::create();
$paymentIntent = \Stripe\PaymentIntent::create([
'customer' => $customer->id,
'setup_future_usage' => 'off_session',
'amount' => config('app.PRICE_TNT6WEEK') * 100, //6week program
'currency' => 'usd',
'automatic_payment_methods' => [
'enabled' => true,
],
'description' => $productID,
]);
$output = [
'clientSecret' => $paymentIntent->client_secret,
];
\Log::debug(['<pre>'.print_r($request->toArray(), true).'</pre>', $productID, $output]);
return response()->json($output);
My checkout.js:
// This is your test publishable API key.
const stripe = Stripe('{{config('app.STRIPE_KEY')}}');
// The items the customer wants to buy
const items = [{ id: "{{$productName}}"}];
let elements;
initialize();
checkStatus();
document
.querySelector("#payment-form")
.addEventListener("submit", handleSubmit);
// Fetches a payment intent and captures the client secret
async function initialize() {
const { clientSecret } = await fetch("{{route('prepare.product', [$slug, $slugVersion])}}", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ items }),
}).then((r) => r.json());
elements = stripe.elements({ clientSecret });
const paymentElementOptions = {
layout: "tabs",
};
const paymentElement = elements.create("payment", paymentElementOptions);
paymentElement.mount("#payment-element");
// Create and mount the Address Element in billing mode
const addressElement = elements.create("address", {
mode: "billing",
defaultValues: {
name: 'Your Full Name...',
address: {
line1: 'Address...',
city: 'City...',
state: 'CA',
postal_code: '',
country: 'US',
},
},
fields: {
phone: 'always',
},
});
addressElement.mount("#address-element");
}
async function handleSubmit(e) {
e.preventDefault();
setLoading(true);
//--- There we should make an additional ajax request with user data
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// Make sure to change this to your payment completion page
return_url: "{{route('upsell.show', [$slug, $slugVersion])}}",
payment_method_data: {
billing_details: {
name: document.getElementById("full_name").value,
email: document.getElementById("email").value,
phone: document.getElementById("phone").value,
}
},
},
});
// This point will only be reached if there is an immediate error when
// confirming the payment. Otherwise, your customer will be redirected to
// your `return_url`. For some payment methods like iDEAL, your customer will
// be redirected to an intermediate site first to authorize the payment, then
// redirected to the `return_url`.
if (error.type === "card_error" || error.type === "validation_error") {
showMessage(error.message);
} else {
showMessage("An unexpected error occurred.");
}
setLoading(false);
}
// Fetches the payment intent status after payment submission
async function checkStatus() {
const clientSecret = new URLSearchParams(window.location.search).get(
"payment_intent_client_secret"
);
if (!clientSecret) {
return;
}
const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret);
switch (paymentIntent.status) {
case "succeeded":
showMessage("Payment succeeded!");
break;
case "processing":
showMessage("Your payment is processing.");
break;
case "requires_payment_method":
showMessage("Your payment was not successful, please try again.");
break;
default:
showMessage("Something went wrong.");
break;
}
}
// ------- UI helpers -------
function showMessage(messageText) {
const messageContainer = document.querySelector("#payment-message");
messageContainer.classList.remove("hidden");
messageContainer.textContent = messageText;
setTimeout(function () {
messageContainer.classList.add("hidden");
messageText.textContent = "";
}, 4000);
}
// Show a spinner on payment submission
function setLoading(isLoading) {
if (isLoading) {
// Disable the button and show a spinner
document.querySelector("#submit").disabled = true;
document.querySelector("#spinner").classList.remove("hidden");
document.querySelector("#button-text").classList.add("hidden");
} else {
document.querySelector("#submit").disabled = false;
document.querySelector("#spinner").classList.add("hidden");
document.querySelector("#button-text").classList.remove("hidden");
}
}

After collecting billing details with either the Address Element, or supplying the payment_method_data[billing_details], that information will be available on Payment Method billing_details (API ref). You can get this by either retrieving the Payment Method, or by retrieving the associated Payment Intent and using expansion to include the full Payment Method object with expand[]=payment_method

Related

How display payment succeeded without return_url param in Stripe

I have one problem in integration Stripe into my React application. I use code from official Stripe documentation. It works expected. My question is how to check is payment succeeded without using return_url ? Am I required to use return url ? I found in Stripe documentation redirect: "if_required" option, but that doesnt make anything. I just get error problem in my console if I put this object in confirmPayment method. I would like have scenario is payment successfull that client navigate to some Confirmation page and to get message payment successfully.
App.jsx
import { loadStripe } from "#stripe/stripe-js";
import { Elements } from "#stripe/react-stripe-js";
import CheckoutForm from "./CheckoutForm";
import "./App.css";
// Make sure to call loadStripe outside of a component’s render to avoid
// recreating the Stripe object on every render.
// This is your test publishable API key.
const stripePromise = loadStripe("pk_test_51LmE9VAoYs2flpvClDqeh0f1vhaDUkBM0bRGaJgThjtaMd3PiPUGQOHjn9f7XW1HGgSQBvTq3xoLy9PovlWLPUnR0031srjgyb");
export default function App() {
const [clientSecret, setClientSecret] = useState("");
useEffect(() => {
// Create PaymentIntent as soon as the page loads
fetch("/create-payment-intent", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ items: [{ id: "xl-tshirt" }] }),
})
.then((res) => res.json())
.then((data) => setClientSecret(data.clientSecret));
}, []);
const appearance = {
theme: 'stripe',
};
const options = {
clientSecret,
appearance,
};
return (
<div className="App">
{clientSecret && (
<Elements options={options} stripe={stripePromise}>
<CheckoutForm />
</Elements>
)}
</div>
);
}
CheckoutForm.jsx
import {
PaymentElement,
useStripe,
useElements
} from "#stripe/react-stripe-js";
export default function CheckoutForm() {
const stripe = useStripe();
const elements = useElements();
const [message, setMessage] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (!stripe) {
return;
}
const clientSecret = new URLSearchParams(window.location.search).get(
"payment_intent_client_secret"
);
if (!clientSecret) {
return;
}
stripe.retrievePaymentIntent(clientSecret).then(({ paymentIntent }) => {
switch (paymentIntent.status) {
case "succeeded":
setMessage("Payment succeeded!");
break;
case "processing":
setMessage("Your payment is processing.");
break;
case "requires_payment_method":
setMessage("Your payment was not successful, please try again.");
break;
default:
setMessage("Something went wrong.");
break;
}
});
}, [stripe]);
const handleSubmit = async (e) => {
e.preventDefault();
if (!stripe || !elements) {
// Stripe.js has not yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}
setIsLoading(true);
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// Make sure to change this to your payment completion page
return_url: "http://localhost:3000",
},
});
// This point will only be reached if there is an immediate error when
// confirming the payment. Otherwise, your customer will be redirected to
// your `return_url`. For some payment methods like iDEAL, your customer will
// be redirected to an intermediate site first to authorize the payment, then
// redirected to the `return_url`.
if (error.type === "card_error" || error.type === "validation_error") {
setMessage(error.message);
} else {
setMessage("An unexpected error occurred.");
}
setIsLoading(false);
};
return (
<form id="payment-form" onSubmit={handleSubmit}>
<PaymentElement id="payment-element" />
<button disabled={isLoading || !stripe || !elements} id="submit">
<span id="button-text">
{isLoading ? <div className="spinner" id="spinner"></div> : "Pay now"}
</span>
</button>
{/* Show any error or success messages */}
{message && <div id="payment-message">{message}</div>}
</form>
);
}
When using redirect: 'if_required', then the return_url attribute becomes not required.
If no redirection is required then you need to wait for the confirmation from the method stripe.confirmPayment and check if there is an error in the response.
To do so, you can adapt your CheckoutForm.jsx file and adapt your function handleSubmit like below:
setIsLoading(true);
const response = await stripe.confirmPayment({
elements,
confirmParams: {
},
redirect: 'if_required'
});
if (response.error) {
showMessage(response.error.message);
} else {
showMessage(`Payment Succeeded: ${response.paymentIntent.id}`);
}
setIsLoading(false);
Also, if you want to get notified from your backend when a successful payment has occurred, you can set a webhook[1] and listen to this Stripe event payment_intent.succeeded[2]
[1] https://stripe.com/docs/webhooks
[2] https://stripe.com/docs/api/events/types#event_types-payment_intent.succeeded

Saving and Reading UserState in Botframework v4

Hello I'm having a hard time dealing with UserStates in MSBF
Here's the setup of the dialogBot.ts
export class DialogBot extends ActivityHandler {
private conversationState: BotState;
private userState: BotState;
private dialog: Dialog;
private dialogState: StatePropertyAccessor<DialogState>;
/**
*
* #param {BotState} conversationState
* #param {BotState} userState
* #param {Dialog} dialog
*/
constructor(
conversationState: BotState,
userState: BotState,
dialog: Dialog
) {
super();
if (!conversationState) {
throw new Error(
'[DialogBot]: Missing parameter. conversationState is required'
);
}
if (!userState) {
throw new Error('[DialogBot]: Missing parameter. userState is required');
}
if (!dialog) {
throw new Error('[DialogBot]: Missing parameter. dialog is required');
}
this.conversationState = conversationState as ConversationState;
this.userState = userState as UserState;
this.dialog = dialog;
this.dialogState =
this.conversationState.createProperty<DialogState>('DialogState');
this.onMessage(async (context, next) => {
console.log('Running dialog with Message Activity.');
// Run the Dialog with the new message Activity.
await (this.dialog as MainDialog).run(context, this.dialogState);
// By calling next() you ensure that the next BotHandler is run.
await next();
});
this.onDialog(async (context, next) => {
// Save any state changes. The load happened during the execution of the Dialog.
await this.conversationState.saveChanges(context, false);
await this.userState.saveChanges(context, false);
// By calling next() you ensure that the next BotHandler is run.
await next();
});
}
}
In the MainDialog.ts I'm fetching a user from the database based on the userID passed on and if it fetches anything it should be saved in the UserState.
mainDialog.ts
export class MainDialog extends CancelAndHelpDialog {
private userProfileAccessor: StatePropertyAccessor<any>;
userState: UserState;
constructor(
bookingDialog: BookingDialog,
userState: UserState,
conversationState: ConversationState
) {
super('MainDialog');
// DECLARE DIALOGS HERE
const createJobOrderDialog = new CreateJobOrderDialog(
'createJobOrderDialog'
);
const checkJobOrderStatusDialog = new CheckJobOrderStatusDialog(
'checkJobOrderStatusDialog'
);
const accountSetupDialog = new AccountSetupDialog(
'accountSetupDialog',
userState
);
this.userProfileAccessor = userState.createProperty('userProfile');
this.userState = userState;
// Define the main dialog and its related components.
// This is a sample "book a flight" dialog.
this.addDialog(new TextPrompt('TextPrompt'));
this.addDialog(bookingDialog);
this.addDialog(createJobOrderDialog);
this.addDialog(checkJobOrderStatusDialog);
this.addDialog(accountSetupDialog);
this.addDialog(
new WaterfallDialog(MAIN_WATERFALL_DIALOG, [
this.accountSetupStep.bind(this),
this.introStep.bind(this),
this.actStep.bind(this),
this.finalStep.bind(this)
])
);
this.initialDialogId = MAIN_WATERFALL_DIALOG;
}
/**
* The run method handles the incoming activity (in the form of a DialogContext) and passes it through the dialog system.
* If no dialog is active, it will start the default dialog.
* #param {TurnContext} context
*/
public async run(
context: TurnContext,
accessor: StatePropertyAccessor<DialogState>
) {
const dialogSet = new DialogSet(accessor);
dialogSet.add(this);
const dialogContext = await dialogSet.createContext(context);
const results = await dialogContext.continueDialog();
if (results.status === DialogTurnStatus.empty) {
await dialogContext.beginDialog(this.id);
}
}
private async accountSetupStep(
stepContext: WaterfallStepContext
): Promise<DialogTurnResult> {
const userProfile = await this.userProfileAccessor.get(
stepContext.context,
{}
);
stepContext.context.activity.from.id = '*******************';
userProfile.isHandover = false;
await this.userProfileAccessor.set(stepContext.context, userProfile);
// await this.userState.saveChanges(stepContext.context, true);
const result = await userService.getUser(
stepContext.context.activity.from.id
);
console.log(result);
if (Object.keys(result).length === 0) {
return await stepContext.beginDialog('accountSetupDialog');
} else {
userProfile.user = result;
await this.userProfileAccessor.set(stepContext.context, userProfile);
// await this.userState.saveChanges(stepContext.context, true);
return await stepContext.next();
}
}
private async introStep(
stepContext: WaterfallStepContext
): Promise<DialogTurnResult> {
const userProfile = await this.userProfileAccessor.get(
stepContext.context,
{}
);
console.log('INTRO STEP USERPROFILE', userProfile);
await stepContext.context.sendActivities([
{
type: 'message',
text: `Hi ${userProfile.user.first_name}, welcome to Podmachine. Let us take care of the dirty stuff so you can sound like a Pro!`
},
{
type: 'typing'
},
{ type: 'delay', value: 1000 },
{
type: 'message',
text: 'To start, you need to submit a job order.'
},
{
type: 'typing'
},
{ type: 'delay', value: 1000 },
{
type: 'message',
text: `So what's a job order? It's basically sending a request to edit (1) one raw episode audio file to Podmachine team. We'll handle the rest. `
},
{
type: 'typing'
},
{ type: 'delay', value: 1000 },
{
type: 'message',
text: `Since you're part of the early access users (Yay!), you're entitled to (1) one free job order / edit. Go ahead and click "Create New Job order."`
},
{
type: 'typing'
},
{ type: 'delay', value: 1000 }
]);
const messageText = (stepContext.options as any).restartMsg
? (stepContext.options as any).restartMsg
: `Please take note that once you submit your job order, Podmachine team will review it first. Make sure all the details you put in your job order are correct. It will be our basis when we do the edits. Thank you!`;
const promptMessage = MessageFactory.suggestedActions(
[
'Create New Job Order',
'Check Status',
'Chat with Team',
'Subscribe Now'
],
messageText
);
return await stepContext.prompt('TextPrompt', {
prompt: promptMessage
});
}
/**
* Second step in the waterall. This will use LUIS to attempt to extract the origin, destination and travel dates.
* Then, it hands off to the bookingDialog child dialog to collect any remaining details.
*/
private async actStep(
stepContext: WaterfallStepContext
): Promise<DialogTurnResult> {
// const bookingDetails = new BookingDetails();
const userProfile = await this.userProfileAccessor.get(stepContext.context, {});
console.log('USER PROFILE ACT STEP', userProfile);
switch (stepContext.result) {
case 'Create New Job Order':
return await stepContext.beginDialog('createJobOrderDialog');
break;
case 'Check Status':
return await stepContext.beginDialog('checkJobOrderStatusDialog');
break;
case 'Chat with Team':
userProfile.isHandover = true;
await stepContext.context.sendActivity(
`Hi ${userProfile.user.first_name}, we're glad to assist you. Please type your concern below. A Podmachine associate will getback to you within 3-5 minutes. Thank you for your patience.`
);
await this.userProfileAccessor.set(stepContext.context, userProfile);
return await stepContext.endDialog();
break;
case 'Upgrade Now':
await stepContext.context.sendActivity(
`Redirecting to Upgrade Now page...`
);
return await stepContext.endDialog();
break;
case 'Schedule a Checkpoint Meeting':
await stepContext.context.sendActivity(`Feature in progress...`);
return await stepContext.endDialog();
break;
default:
break;
}
return await stepContext.next();
// return await stepContext.beginDialog('bookingDialog', bookingDetails);
}
I can see the saved user details in the introStep but when it comes to the actStep I no longer see the value and it comes out undefined. Can you help me with implementing UserState because I'm not sure if I'm doing it correctly by loading it, the samples from github is not as clear.
USER PROFILE ACT STEP {}
[onTurnError] unhandled error: DialogContextError: Cannot read properties of undefined (reading 'first_name')
Looks like your bot aren't storing the state, so it can't recover it on the next turn.
Are you setting somewhere the storage your bot are using?
Check this doc on how to use storages:
https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-storage?view=azure-bot-service-4.0&tabs=javascript

How to pay for multiple subscriptions at once for the first time. Stripe

How to pay for multiple subscriptions at once for the first time.
A customer can choose multiple subscriptions, some of them are with monthly payment and others with half-yearly payment.
When the customer chooses the subscriptions he adds them to the cart.
I do this on the server side for the creation of a customer
(stripe.com/docs/billing/subscriptions/elements#create-customer)
$customer = $stripe->customers->create([
'email' => $this->getUser()->getEmail(),
]);
then I create the subscriptions
(https://stripe.com/docs/billing/subscriptions/multiple)
$sub1 = $stripe->subscriptions->create([
'customer' => 'cus_4fdAW5ftNQow1b',
'items' => [['price' => 'price_CZB2krKbBDOkTe']],
'payment_behavior' => 'default_incomplete',
'expand' => ['latest_invoice.payment_intent'],
]);
$sub2 = $stripe->subscriptions->create([
'customer' => 'cus_4fdAW5ftNQow1b',
'items' => [['price' => 'price_CZB1AX3KOacNJb']],
'payment_behavior' => 'default_incomplete',
'expand' => ['latest_invoice.payment_intent'],
]);
However, in the doc cite:
"On the backend, create the subscription with status incomplete using payment_behavior=default_incomplete and returns the client_secret from the payment intent to the frontend to complete payment."
The problem I have is that I have one client_secret for each subscription. So, how to pay (for the first time, start of the subscriptions) in one credit card form.
In the front end I have this:
let stripePublicKey = "{{ stripePublicKey }}"
let stripe = Stripe(stripePublicKey);
let elements = stripe.elements();
//let clientSecret = {{clientSecret}} // the secret client is requested in the function stripe.confirmCardPayment below
//Create an instance of an Element and mount it to the Element container
let card = elements.create('card');
card.mount('#card-element');
function displayError(event) {
//changeLoadingStatePrices(false);
let displayError = document.getElementById('card-element-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
}
card.addEventListener('change', function (event) {
displayError(event);
});
const btn = document.querySelector('#submit-payment-btn');
btn.addEventListener('click', async (e) => {
e.preventDefault();
const nameInput = document.getElementById('name');
// Create payment method and confirm payment intent.
stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: cardElement,
billing_details: {
name: nameInput.value,
},
}
}).then((result) => {
if(result.error) {
alert(result.error.message);
} else {
// Successful subscription payment
}
});
});
Is this possible? If not what approach should I consider?
Thanks!
UPDATE
If I make a loop to pay, is it wrong?
const btn = document.querySelector('#submit-payment-btn');
btn.addEventListener('click', async (e) => {
e.preventDefault();
for(const [key, value] of Object.entries(clientSecrets)) {
// Create payment method and confirm payment intent.
await stripe.confirmCardPayment(value.secret, {
payment_method: {
card: card,
billing_details: {
name: '{{app.user.fullName}}'
},
}
}).then((result) => {
if (result.error) {
alert(result.error.message);
} else {
// Successful subscription payment
console.log('Successful subscription payment');
}
});
}
});
Can it cause problems?
You can't do this the way you're trying, as you noted these are separate Payment Intents (each with their own client_secret).
When you follow the default_incomplete integration path, when you confirm the payment intent from the first subscription, the payment method will be attached to the customer and ready for future usage. Once that's successful, you and use that attached payment method to create the second subscription, either by providing the default_payment_method explicitly (API ref), or by setting the customer invoice_settings[default_payment_method] first (API ref). You may still encounter an authentication request, at the discretion of your customer's bank, so make sure you check for and handle those cases.
$sub1 = $stripe->subscriptions->create([
'customer' => 'cus_4fdAW5ftNQow1b',
'items' => [
['price' => 'price_CZB2krKbBDOkTe'],
['price' => 'price_CZB1AX3KOacNJb']
],
'payment_behavior' => 'default_incomplete',
'expand' => ['latest_invoice.payment_intent'],
]);

Stripe API - stipe.setupIntents doesn't exist

I am trying to delete setup intent via
stripe.setupIntents.delete
but that method doesn't exist. Any idea what am I doing wrong (I am looking at the official documents, stripe.setupIntents is what I need here).
I have the following code:
<script src="https://js.stripe.com/v3/"></script>
<script>
const stripe = Stripe('{{ env('STRIPE_KEY') }}');
const elements = stripe.elements();
const cardElement = elements.create('card', {
hidePostalCode: true,
});
cardElement.mount('#card-element');
const cardHolderName = document.getElementById('card-holder-name');
const cardButton = document.getElementById('card-button');
const clientSecret = cardButton.dataset.secret;
cardButton.addEventListener('click', async (e) => {
e.preventDefault()
if (cardHolderName.value.length < 3) {
$(cardHolderName).addClass('is-invalid')
.parent()
.append('<span class="text-danger">Please Insert Card Holder Name.</span>');
return;
} else {
$(cardHolderName).removeClass('is-invalid');
$(cardHolderName).next('.text-danger').remove();
}
let id = $(cardButton).data('id')
let url = $(cardButton).data('url')
const { setupIntent, error } = await stripe.confirmCardSetup(
clientSecret, {
payment_method: {
card: cardElement,
billing_details: {
name: cardHolderName.value
}
}
}
)
if (error) {
let errorElement = document.getElementById('card-errors');
errorElement.textContent = error.message;
} else {
let billingForm = $('#billing-form')
$.ajax({
url: url,
method: 'POST',
cache: false,
data: {
cardholder: billingForm.find('#cardholder').val(),
locum_organization: billingForm.find('#locum_organization').val(),
billing_addresses: billingForm.find('#billing_addresses').val(),
intent: setupIntent,
id: id
},
success: function(response) {
},
fail: function(xhr, textStatus, errorThrown){
console.log(errorThrown);
},
error(response){
stripe.setupIntents.cancel(
setupIntent.id,
function(err, setupIntent) {
// asynchronously called
})
}
});
}
});
</script>
You can only cancel a setup intent; not delete it. Also, setup intents can only be cancelled server-side. Right now it looks like you're trying to cancel it client-side with Stripe.js (which doesn't have a cancel setup intent method). It also isn't necessary to cancel a setup intent after it's gone through. In fact, you can't cancel a setup intent at all unless it's in one of these four processing states:
requires_payment_method, requires_capture, requires_confirmation, requires_action.
https://stripe.com/docs/api/setup_intents/cancel?lang=node
Your integration seems fine other than that. I would recommend leaving out the cancellation step entirely, and you should be good to go!

How to create and send a backchannel event with every message in bot framework?

I'm trying to access a session variable from the botbuilder middleware in send hook:
bot.use({
botbuilder: function (session, next) {
session.send(); // it doesn't work without this..
session.sendTyping();
console.log('session.userData', session.userData['UI_Changes']);
var reply = createEvent("UI_Changes", session.userData['UI_Changes'], session.message.address);
session.send(reply);
// session.userData['UI_Changes'] = {};
next();
},
send: function (event, next) {
// console.log('session.userData', session.userData['UI_Changes']);
// var reply = createEvent("UI_Changes", session.userData['UI_Changes'], session.message.address);
// session.send(reply);
// session.userData['UI_Changes'] = {};
next();
}
});
But since session is not available in send, how can I access the userData?
createEvent simply creates a backchannel event:
//Creates a backchannel event
const createEvent = (eventName, value, address) => {
var msg = new builder.Message().address(address);
msg.data.type = "event";
msg.data.name = eventName;
msg.data.value = value;
return msg;
}
I found this answer on stackoverflow:
send: function (message, next) {
bot.loadSessionWithoutDispatching(message.address,function (error,session){
console.log(session.userData);
});
next();
}
But, when I try to create an event and send it, I'm not able to access the address
bot.loadSessionWithoutDispatching(event.address, function (error,session){
console.log('session not null?', session !== null ? "yes" : "no");
if(session !== null){
console.log('session.userData', session.userData['UI_Changes'], 'address:', session);
var reply = createEvent("UI_Changes", session.userData['UI_Changes'], event.address); //undefined
session.send(reply);
session.userData['UI_Changes'] = {};
}
});
both session.message and event.address are undefined inside the callback function. How can I possibly do a workaround?
event has following content:
event: { type: 'message',
text: 'You reached the Greeting intent. You said \'hi\'.',
locale: 'en-US',
localTimestamp: '2018-06-21T14:37:12.684Z',
from: { id: 'Steves#4MRN9VFFpAk', name: 'Steves' },
recipient: { id: 'pruthvi', name: 'pruthvi' },
inputHint: 'acceptingInput' }
whereas outside the loadSessionWithoutDispatching function it has:
event outside { type: 'message',
agent: 'botbuilder',
source: 'directline',
textLocale: 'en-US',
address:
{ id: 'A7nrBS4yINt2607QtKpxOP|0000048',
channelId: 'directline',
user: { id: 'pruthvi', name: 'pruthvi' },
conversation: { id: 'A7nrBS4yINt2607QtKpxOP' },
bot: { id: 'Steves#4MRN9VFFpAk', name: 'Steves' },
serviceUrl: 'https://directline.botframework.com/' },
text: 'You reached the Greeting intent. You said \'hi\'.' }
I've used bot.loadSession instead of loadSessionWithoutDispatching and it works fine.
I've used bot.loadSession instead of loadSessionWithoutDispatching and it works fine.

Resources