Botbuilder adaptive dialog storing user input - c#

I'm using previews of the botbuilder adaptive dialog to gather some user info. I want to store this information in SQL. So my question is how can I gather the info from the "Property" in the textinput?
new TextInput
{
Prompt = new ActivityTemplate(question.Text),
Property = "user.userProfile" + question.Id
}

Use CodeAction or HttpRequest to call your api to store the information
Use this to make HTTP requests to any endpoint.
new HttpRequest()
{
// Set response from the http request to turn.httpResponse property in memory.
ResultProperty = "turn.httpResponse",
Method = HttpRequest.HttpMethod.POST,
Headers = new Dictionary<string,string> (), /* request header */
Body = JToken.FromObject(new
{
data = "#{user.userProfile" + question.Id + "}",
another = "#{user.another}"
}) /* request body */
});
Code Action
private async Task<DialogTurnResult> CodeActionSampleFn(DialogContext dc, System.Object options)
{
var userState = JObject.FromObject(dc.GetState().FirstOrDefault(x => x.Key == "user").Value);
//Get your data here
var data = userState.Value<JObject>("userProfile" + question.Id);
// call your API by HttpClient
//...
return dc.ContinueDialogAsync();
}
Check out more detail here
https://github.com/microsoft/BotBuilder-Samples/blob/master/experimental/adaptive-dialog/docs/recognizers-rules-steps-reference.md#HttpRequest

You can see examples of how to access state properties all over this page:
new SendActivity("Hello, #{user.name}")

Related

Encode parameter before executing the request

We are using .NET Core 3.1 and Swashbuckle.AspNetCore 5.6.3.
Some of our controller actions require User-Identity HTTP header to be sent in the request. The value of this User-Identity HTTP header is base64-encoded JSON object. Example:
JSON object
{
"email": "test#test.com"
}
is encoded in base64 as:
ewogICJFbWFpbCI6ICJ0ZXN0QHRlc3QuY29tIgp9.
We implemented the following filter operation which checks if controller action has [RequireUserIdentityFilterAttribute] attribute and adds a parameter accordingly.
public class RequireUserIdentityOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
// check if controller action has RequireUserIdentityFilterAttribute with reflection
var hasRequireUserIdentityFilterAttribute = context.MethodInfo
.GetCustomAttributes(true)
.OfType<RequireUserIdentityFilterAttribute>()
.Any();
if (hasRequireUserIdentityFilterAttribute)
{
operation.Parameters.Add(new OpenApiParameter
{
Description = "Base-64 encoded user email object. Example: { \"Email\": \"test#test.com\" } => ewogICJFbWFpbCI6ICJ0ZXN0QHRlc3QuY29tIgp9",
Name = "User-Identity",
In = ParameterLocation.Header,
Schema = new OpenApiSchema
{
Type = "string",
Example = new OpenApiString("ewogICJFbWFpbCI6ICJ0ZXN0QHRlc3QuY29tIgp9")
}
});
}
}
}
This is how it looks like in SwaggerUI:
It works fine as-is. User-Identity header is sent to the server.
However, it is not very user-friendly because user has to input a string which must be base64-encoded. Is it possible that we simplify the input so that the user will only have to input user email (ex. test#test.com) and C# will handle the base64 encoding before the request is sent to the server?
SwaggerUI uses JavaScript to send the requests to the server. You could inject a script that intercepts the request and changes the header before transmitting it to the server, e.g.
app.UseSwaggerUI(c =>
{
// ...
var adjustHeaders = #"(request) => {
let header = request.headers["User-Identity"];
if (header && header.length > 0) {
// header is a JSON object
if (header[0] == "{") header =
btoa(header);
// header is an email address
else if (header.indexOf("#") >= 0)
header = btoa(JSON.stringify({ email: header }));
// Otherwise assume that it is already encoded
request.headers["User-Identity"] = header;
}
return request;
}";
c.UseRequestInterceptor(adjustHeaders);
});
Above script is a pseudo-Javascript that can give you a starting point. Please test it in the browser whether it works in your situation and for the browsers your SwaggerUI targets.

Azure Function and SharePoint webhook: not getting changes from SharePoint list

I'm using a couple of Azure Functions with SharePoint webhook.
The first function is the one used to save messages from SharePoint webhook to a queue (Azure storage queue). This is the function content:
[FunctionName("QueueFunction")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
log.Info($"Webhook was triggered!");
// Grab the validationToken URL parameter
string validationToken = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "validationtoken", true) == 0)
.Value;
// If a validation token is present, we need to respond within 5 seconds by
// returning the given validation token. This only happens when a new
// web hook is being added
if (validationToken != null)
{
log.Info($"Validation token {validationToken} received");
var response = req.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(validationToken);
return response;
}
log.Info($"SharePoint triggered our webhook...great :-)");
var content = await req.Content.ReadAsStringAsync();
log.Info($"Received following payload: {content}");
var notifications = JsonConvert.DeserializeObject<ResponseModel<NotificationModel>>(content).Value;
log.Info($"Found {notifications.Count} notifications");
if (notifications.Count > 0)
{
// get the cloud storage account
string queueName = "MYQUEUE";
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(Environment.GetEnvironmentVariable("AzureWebJobsStorage"));
CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient();
CloudQueue queue = queueClient.GetQueueReference(queueName);
await queue.CreateIfNotExistsAsync();
// store each notification as a queue item
foreach (var notification in notifications)
{
string message = JsonConvert.SerializeObject(notification);
log.Info($"Adding to {queueName}: {message}");
await queue.AddMessageAsync(new CloudQueueMessage(message));
log.Info($"added.");
}
// if we get here we assume the request was well received
return new HttpResponseMessage(HttpStatusCode.OK);
}
The message in queue is correctly added.
Then I've another function triggered by queue. This is the code of the function:
[FunctionName("OCRFunction")]
public static void Run([QueueTrigger("MYQUEUE", Connection = "QueueConn")]string myQueueItem, TraceWriter log)
{
log.Info($"C# Queue trigger function processed: {myQueueItem}");
string siteUrl = "https://MYSHAREPOINT.sharepoint.com/sites/MYSITE";
log.Info($"Processing notifications...");
string json = myQueueItem;
var data = (JObject)JsonConvert.DeserializeObject(json);
string notificationResource = data["resource"].Value<string>();
ClientContext SPClientContext = LoginSharePoint(siteUrl);
log.Info($"Logged in SharePoint");
GetChanges(SPClientContext, notificationResource, log);
}
public static ClientContext LoginSharePoint(string BaseUrl)
{
// Login using UserOnly Credentials (User Name and User PW)
ClientContext cntReturn;
string myUserName = config["spUN"];
string myPassword = config["spPWD"];
SecureString securePassword = new SecureString();
foreach (char oneChar in myPassword) securePassword.AppendChar(oneChar);
SharePointOnlineCredentials myCredentials = new SharePointOnlineCredentials(myUserName, securePassword);
cntReturn = new ClientContext(BaseUrl);
cntReturn.Credentials = myCredentials;
return cntReturn;
}
static void GetChanges(ClientContext SPClientContext, string ListId, TraceWriter log)
{
Web spWeb = SPClientContext.Web;
List myList = spWeb.Lists.GetByTitle("MY LIST");
SPClientContext.Load(myList);
SPClientContext.ExecuteQuery();
ChangeQuery myChangeQuery = GetChangeQueryNew(ListId);
var allChanges = myList.GetChanges(myChangeQuery);
SPClientContext.Load(allChanges);
SPClientContext.ExecuteQuery();
log.Info($"---- Changes found : " + allChanges.Count());
foreach (Change oneChange in allChanges)
{
if (oneChange is ChangeItem)
{
int myItemId = (oneChange as ChangeItem).ItemId;
log.Info($"---- Changed ItemId : " + myItemId);
ListItem myItem = myList.GetItemById(myItemId);
Microsoft.SharePoint.Client.File myFile = myItem.File;
ClientResult<System.IO.Stream> myFileStream = myFile.OpenBinaryStream();
SPClientContext.Load(myFile);
SPClientContext.ExecuteQuery();
byte[] myFileBytes = ConvertStreamToByteArray(myFileStream);
[...] SOME CODE HERE [...]
myItem["OCRText"] = myText;
myItem.Update();
SPClientContext.ExecuteQuery();
log.Info($"---- Text Analyze OCR added to SharePoint Item");
}
}
}
public static ChangeQuery GetChangeQueryNew(string ListId)
{
ChangeToken lastChangeToken = new ChangeToken();
lastChangeToken.StringValue = string.Format("1;3;{0};{1};-1", ListId, DateTime.Now.AddMinutes(-1).ToUniversalTime().Ticks.ToString());
ChangeToken newChangeToken = new ChangeToken();
newChangeToken.StringValue = string.Format("1;3;{0};{1};-1", ListId, DateTime.Now.ToUniversalTime().Ticks.ToString());
ChangeQuery myChangeQuery = new ChangeQuery(false, false);
myChangeQuery.Item = true; // Get only Item changes
myChangeQuery.Add = true; // Get only the new Items
myChangeQuery.ChangeTokenStart = lastChangeToken;
myChangeQuery.ChangeTokenEnd = newChangeToken;
return myChangeQuery;
}
public static Byte[] ConvertStreamToByteArray(ClientResult<System.IO.Stream> myFileStream)
{
Byte[] bytReturn = null;
using (System.IO.MemoryStream myFileMemoryStream = new System.IO.MemoryStream())
{
if (myFileStream != null)
{
myFileStream.Value.CopyTo(myFileMemoryStream);
bytReturn = myFileMemoryStream.ToArray();
}
}
return bytReturn;
}
public static async Task<TextAnalyzeOCRResult> GetAzureTextAnalyzeOCR(byte[] myFileBytes)
{
TextAnalyzeOCRResult resultReturn = new TextAnalyzeOCRResult();
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "XXXXXXXXXXXXXXXXXXXX");
string requestParameters = "language=unk&detectOrientation=true";
/* OCR API */
string uri = "https://MYOCRSERVICE.cognitiveservices.azure.com/vision/v3.0/ocr" + "?" + requestParameters;
string contentString = string.Empty;
HttpResponseMessage response;
using (ByteArrayContent content = new ByteArrayContent(myFileBytes))
{
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
response = await client.PostAsync(uri, content);
contentString = await response.Content.ReadAsStringAsync();
resultReturn = JsonConvert.DeserializeObject<TextAnalyzeOCRResult>(contentString);
return resultReturn;
}
}
Before current approach with two functions, I was using a single function where I managed the notifications and I executed some code to update a field in my SharePoint list. This method was having some problem when I was receiving many notifications from SharePoint so I decided to use queue as suggested in Microsoft documentation. This solution was working fine with a single notification received and my SharePoint list item were updated without problem.
To avoid problems with multiple notification, I decided to split functions, one registering notifications in a queue and the other executing some operations and updating a SharePoint field.
The first one function QueueFunction is working fine, the second one is triggering correctly but it is not getting changes from SharePoint list even if I just add one item.
I've tried to check GetChanges code to find why it is always returning no changes, but the code is the same of the one I used when I had only one function, so I can't understand why the behaviour is changed.
What's wrong with my approach? Is there something I could do to correct the second function?
According to the comments, just summarize the solution as below for other communities reference:
Use a function to save the message in a queue and then call an azure web job, the problem was caused by the the running time of the function may exceed 5 minutes.
By the way, the default timeout of azure function(with consumption plan) is 5 minutes, we can see all of the default timeout for different plan on this page (also shown as below screenshot).
If we want longer timeout, we can set the functionTimeout property in host.json of the function(but can not exceed the Maximum timeout). Or we can also use higher plan for the function app, such as Premium plan and App Service plan.

Server Events Client - Getting rid of the automatically appended string at the end of the URI

I am new to the Service Stack library and trying to use the Server Events Client. The server I'm working with has two URIs. One for receiving a connection token and one for listening for search requests using the token acquired in the previous call.
I use a regular JsonServiceClient with digest authentication to get the token like so:
public const string Baseurl = "http://serverIp:port";
var client = new JsonServiceClient(Baseurl)
{
UserName = "user",
Password = "password",
AlwaysSendBasicAuthHeader = false
};
//ConnectionData has a string token property
var connectionData = client.Get<ConnectionData>("someServices/connectToSomeService");
And then use this token to listen for server events. Like so:
var eventClient =
new ServerEventsClient($"{Baseurl}/differentUri/retrieveSearchRequests?token={connectionData.Token}")
{
OnConnect = Console.WriteLine,
OnMessage = message => Console.WriteLine(message.Json),
OnCommand = message => Console.WriteLine(message.Json),
OnException = WriteLine,
ServiceClient = client, //same JsonServiceClient from the previous snippet
EventStreamRequestFilter = request =>
{
request.PreAuthenticate = true;
request.Credentials = new CredentialCache
{
{
new Uri(Baseurl), "Digest", new NetworkCredential("user", "password")
}
};
}
};
Console.WriteLine(eventClient.EventStreamUri); // "/event-stream&channels=" is appended at the end
eventClient.Start();
The problem with the above code is that it automatically appends "/event-stream&channels=" at the end of my URI. How do I disable this behavior?
I have tried adding the following class
public class AppHost : AppSelfHostBase
{
public static void Start()
{
new AppHost().Init().Start(Baseurl);
}
public AppHost() : base(typeof(AppHost).Name, typeof(AppHost).Assembly)
{
}
public override void Configure(Container container)
{
Plugins.Add(new ServerEventsFeature
{
StreamPath = string.Empty
});
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[]
{
new DigestAuthProvider()
}));
}
}
and called Start on it, before calling the above code, but still no luck.
The ServerEventsClient is only for listening to ServiceStack SSE Stream and should only be populated with the BaseUrl of the remote ServiceStack instance, i.e. not the path to the /event-stream or a queryString.
See this previous answer for additional customization available, e.g. you can use ResolveStreamUrl to add a QueryString to the EventStream URL it connects to:
var client = new ServerEventsClient(BaseUrl) {
ResolveStreamUrl = url => url.AddQueryParam("token", token)
});
If you've modified ServerEventsFeature.StreamPath to point to a different path, e.g:
Plugins.Add(new ServerEventsFeature
{
StreamPath = "/custom-event-stream"
});
You can change the ServerEventsClient to subscribe to the custom path with:
client.EventStreamPath = client.BaseUri.CombineWith("custom-event-stream");
ResolveStreamUrl + EventStreamPath is available from v5.0.3 that's now available on MyGet.

How to send files along with the viewModel using Web API or how to save using temporary data

I've read many stackoverflow posts with the similar problems as well as several blogs but I am still uncertain as how to solve my problem :(
I have angularJS directive that allows to upload files to the server. The code is like this:
[HttpPost]
[Route("UploadFile")]
public async Task<HttpResponseMessage> UploadFile()
{
// Check if the request contains multipart/form-data.
if (Request.Content.IsMimeMultipartContent("form-data"))
{
try
{
var resultOut = new List<FileUploadResult>();
var streamProvider = new MultipartMemoryStreamProvider();
streamProvider = await Request.Content.ReadAsMultipartAsync(streamProvider);
foreach (
var item in
streamProvider.Contents.Where(c => !string.IsNullOrEmpty(c.Headers.ContentDisposition.FileName))
)
{
FileUploadResult file = new FileUploadResult()
{
FileName = item.Headers.ContentDisposition.FileName,
// Content = fileBytes, // No need to pass the info back as we're not going to read it save it yet
Key = Guid.NewGuid().ToString(),
Type = item.Headers.ContentDisposition.DispositionType
};
resultOut.Add(file);
//using (Stream stFileSource = new MemoryStream(await item.ReadAsByteArrayAsync())) {
// byte[] fileBytes;
// fileBytes = new Byte[stFileSource.Length];
// stFileSource.Read(fileBytes, 0, Convert.ToInt32(stFileSource.Length));
// FileUploadResult file = new FileUploadResult()
// {
// FileName = item.Headers.ContentDisposition.FileName,
// // Content = fileBytes, // No need to pass the info back as we're not going to read it save it yet
// Key = Guid.NewGuid().ToString(),
// Type = item.Headers.ContentDisposition.DispositionType
// };
// resultOut.Add(file);
//}
}
return Request.CreateResponse(HttpStatusCode.OK, resultOut.ToArray());
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
Also directive saves the Files array into a property. My user form allows to remove some files / add more files and then I want to save the information from the form (somewhat complex view model) along with the files. I was unable to figure that problem so far. One possibility I see here is to save the files in the UploadFile method using Repository into a database. However, I would prefer to save that into some temporary table instead (e.g. #FileInfo table) and not the actual table. Or perhaps there is a way to save files (with its binary content) into some memory object so I will be able to get that content back when I am ready to save my model's data? Can you either show implementation of the temporary repository storage or give some other ideas for my dilemma?
Firstly, Your directive need to create a post request with 'multipart/form-data'.
Check this link for reference.
However, we use angular file upload to do this.
angular
.module('app', ['angularFileUpload'])
.controller('AppController', function($scope, FileUploader) {
$scope.uploader = new FileUploader(
{
url: 'Your/upload/url',
headers: {
'autorization': 'Bearer token if you need it'
},
onProgressItem: function () {
...
},
onSuccessItem: function (opt, data) {
...
},
onErrorItem: function (opt) {
...
}
});
//you may want to wrap the following in an event
var uploadItem = $scope.uploader.queue[uploader.queue.length - 1];
uploadItem.formData.push({
someData: "someData",
moreData: "moreData"
});
uploadItem.upload();
uploadItem.formData = [];
});
Then in your controller, you can do the following to retrieve what you need:
//your request
var request = HttpContext.Current.Request;
//your fields
var someData = request.Form["someData"];
var moreData = request.Form["moreData"];
//your file
var file = request.Files["file"];
Looks like a job for TempData:
TempData in ASP.NET MVC is basically a dictionary object derived from
TempDataDictionary. TempData stays for a subsequent HTTP Request as
opposed to other options (ViewBag and ViewData) those stay only for
current request. So, TempdData can be used to maintain data between
controller actions as well as redirects.
example:
//Controller Action 1 (TemporaryEmployee)
public ActionResult TemporaryEmployee()
{
Employee employee = new Employee
{
EmpID = "121",
EmpFirstName = "Imran",
EmpLastName = "Ghani"
};
TempData["Employee"] = employee;
return RedirectToAction("PermanentEmployee");
}
//Controller Action 2(PermanentEmployee)
public ActionResult PermanentEmployee()
{
Employee employee = TempData["Employee"] as Employee;
return View(employee);
}

paypal express checkout integration payer id is null

i am integrating express checkout in my asp.net mvc application. everything works ok even, response is success but when i try to call "GetExpressCheckoutDetailsResponseDetails" i am getting null in "PayerID". The field below is "requestDetails.PayerID"
public ActionResult PayPalExpressCheckout()
{
PaymentDetailsType paymentDetail = new PaymentDetailsType();
CurrencyCodeType currency = (CurrencyCodeType)EnumUtils.GetValue("GBP", typeof(CurrencyCodeType));
List<PaymentDetailsItemType> paymentItems = new List<PaymentDetailsItemType>();
var AppCart = GetAppCart();
foreach(var item in AppCart.Items)
{
PaymentDetailsItemType paymentItem = new PaymentDetailsItemType();
paymentItem.Name = item.Name;
paymentItem.Description = item.Description;
double itemAmount = Convert.ToDouble(item.Price);
paymentItem.Amount = new BasicAmountType(CurrencyCodeType.GBP, itemAmount.ToString());
paymentItem.Quantity = 1;
paymentItems.Add(paymentItem);
}
paymentDetail.PaymentDetailsItem = paymentItems;
paymentDetail.PaymentAction = (PaymentActionCodeType)EnumUtils.GetValue("Sale", typeof(PaymentActionCodeType));
paymentDetail.OrderTotal = new BasicAmountType(CurrencyCodeType.GBP, (AppCart.TotalPrice).ToString());
List<PaymentDetailsType> paymentDetails = new List<PaymentDetailsType>();
paymentDetails.Add(paymentDetail);
SetExpressCheckoutRequestDetailsType ecDetails = new SetExpressCheckoutRequestDetailsType();
ecDetails.ReturnURL = "http://Orchard.Club/Purchase/PayPalExpressCheckoutAuthorisedSuccess";
ecDetails.CancelURL = "http://Orchard.Club/Purchase/CancelPayPalTransaction";
ecDetails.PaymentDetails = paymentDetails;
SetExpressCheckoutRequestType request = new SetExpressCheckoutRequestType();
ecDetails.FundingSourceDetails = new FundingSourceDetailsType();
//request.Version = "104.0";
ecDetails.LandingPage = LandingPageType.BILLING;
ecDetails.SolutionType = SolutionTypeType.SOLE;
ecDetails.FundingSourceDetails.UserSelectedFundingSource = UserSelectedFundingSourceType.CREDITCARD;
request.SetExpressCheckoutRequestDetails = ecDetails;
SetExpressCheckoutReq wrapper = new SetExpressCheckoutReq();
wrapper.SetExpressCheckoutRequest = request;
Dictionary<string, string> sdkConfig = new Dictionary<string, string>();
sdkConfig.Add("mode", "sandbox");
sdkConfig.Add("account1.apiUsername", "mrhammad-facilitator_api1.hotmail.com");
sdkConfig.Add("account1.apiPassword", "1369812511");
sdkConfig.Add("account1.apiSignature", "AJxdrs7c7cXRinyNUS5p1V4s1m4uAGR.wOJ7KzgkewEYmTOOtHrPgSxR");
PayPalAPIInterfaceServiceService service = new PayPalAPIInterfaceServiceService(sdkConfig);
SetExpressCheckoutResponseType setECResponse = service.SetExpressCheckout(wrapper);
if (setECResponse.Ack.Equals(AckCodeType.SUCCESS))
{
GetExpressCheckoutDetailsReq getECWrapper = new GetExpressCheckoutDetailsReq();
// (Required) A timestamped token, the value of which was returned by SetExpressCheckout response.
// Character length and limitations: 20 single-byte characters
getECWrapper.GetExpressCheckoutDetailsRequest = new GetExpressCheckoutDetailsRequestType(setECResponse.Token);
// # API call
// Invoke the GetExpressCheckoutDetails method in service wrapper object
GetExpressCheckoutDetailsResponseType getECResponse = service.GetExpressCheckoutDetails(getECWrapper);
// Create request object
DoExpressCheckoutPaymentRequestType expressrequest = new DoExpressCheckoutPaymentRequestType();
DoExpressCheckoutPaymentRequestDetailsType requestDetails = new DoExpressCheckoutPaymentRequestDetailsType();
expressrequest.DoExpressCheckoutPaymentRequestDetails = requestDetails;
requestDetails.PaymentDetails = getECResponse.GetExpressCheckoutDetailsResponseDetails.PaymentDetails;
// (Required) The timestamped token value that was returned in the SetExpressCheckout response and passed in the GetExpressCheckoutDetails request.
requestDetails.Token = setECResponse.Token;
// (Required) Unique PayPal buyer account identification number as returned in the GetExpressCheckoutDetails response
requestDetails.PayerID = requestDetails.PayerID;
// (Required) How you want to obtain payment. It is one of the following values:
// * Authorization – This payment is a basic authorization subject to settlement with PayPal Authorization and Capture.
// * Order – This payment is an order authorization subject to settlement with PayPal Authorization and Capture.
// * Sale – This is a final sale for which you are requesting payment.
// Note: You cannot set this value to Sale in the SetExpressCheckout request and then change this value to Authorization in the DoExpressCheckoutPayment request.
requestDetails.PaymentAction = (PaymentActionCodeType)
Enum.Parse(typeof(PaymentActionCodeType), "SALE");
// Invoke the API
DoExpressCheckoutPaymentReq expresswrapper = new DoExpressCheckoutPaymentReq();
expresswrapper.DoExpressCheckoutPaymentRequest = expressrequest;
// # API call
// Invoke the DoExpressCheckoutPayment method in service wrapper object
DoExpressCheckoutPaymentResponseType doECResponse = service.DoExpressCheckoutPayment(expresswrapper);
// Check for API return status
if (doECResponse.Ack.Equals(AckCodeType.FAILURE) ||
(doECResponse.Errors != null && doECResponse.Errors.Count > 0))
{
return RedirectToAction("PostPaymentFailure");
}
else
{
TempData["TransactionResult"] = "Transaction ID:" + doECResponse.DoExpressCheckoutPaymentResponseDetails.PaymentInfo[0].TransactionID + Environment.NewLine + "Payment status" + doECResponse.DoExpressCheckoutPaymentResponseDetails.PaymentInfo[0].PaymentStatus.Value.ToString();
return RedirectToAction("SaveCustomer", "SignupOrLogin");
}
}
else
{
return RedirectToAction("Error", "Purchase");
}
}
As #geewiz mentioned, you're missing the step of redirecting the customer to PayPal to approve the payment.
Refer to How to Create One-Time Payments Using Express Checkout guide on PayPal Developer that outlines the steps involved with Express Checkout.
In your code, you will want to retrieve the EC token to use for the redirect from the setECResponse object and then redirect the customer to the PayPal site using that token:
SetExpressCheckoutResponseType setECResponse = service.SetExpressCheckout(wrapper);
// Note: Add appropriate logic for targeting live URL based on your config settings
var redirectUrl = "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=" + setECResponse.Token;

Categories

Resources