I have implemented Paypal ASP.NET SDK to carry out payments on my website, where the user pays for the items through their Paypal account.
What I would like to do is seperate products that the user has ordered into different Paypal transactions, so that when the client logs into Paypal they can see them as seperate itemised transactions.
The Paypal SDK suggests that this can be done, since the Transactions object is a collection. This is a snippet of my code where I am attempting to send multiple transactions through:
#region Get products
ItemList itemList = new ItemList();
if (_products.Any())
{
List<Item> payPalItems = _products.Select(p => new Item
{
name = p.Name,
currency = _currency,
price = p.Price.ToString("N"),
quantity = p.Quantity.ToString(),
sku = p.Sku
}).ToList();
itemList.items = payPalItems;
}
#endregion
#region Personal Details
// Address for the payment
Address billingAddress = new Address();
if (_personalDetails != null)
{
billingAddress.line1 = _personalDetails.Address1;
billingAddress.line2 = _personalDetails.Address2;
billingAddress.state = _personalDetails.County;
billingAddress.city = _personalDetails.City;
billingAddress.postal_code = _personalDetails.PostCode;
billingAddress.country_code = _personalDetails.CountryCode;
}
#endregion
#region Payment Amount Details
Details paymentDetails = new Details();
paymentDetails.shipping = _delivery.ToString();
paymentDetails.subtotal = _products.Sum(p => p.Total).ToString("N");
paymentDetails.tax = _tax.ToString();
Amount paymentAmount = new Amount();
paymentAmount.currency = _currency;
// Total must be equal to sum of shipping, tax and subtotal.
paymentAmount.total = ((decimal.Parse(paymentDetails.subtotal) * (100 + _tax) / 100) + _delivery).ToString("N");
paymentAmount.details = paymentDetails;
#endregion
#region Transaction --> Sending multiple transactions here...
// A transaction defines the contract of a payment - what is the payment for and who is fulfilling it.
Transaction trans1 = new Transaction();
trans1.amount = paymentAmount;
trans1.item_list = itemList; // Currently sending the same list of items for testing.
trans1.invoice_number = "Surinder Split Order 1";
Transaction trans2 = new Transaction();
trans2.amount = paymentAmount;
trans2.item_list = itemList; // Currently sending the same list of items for testing.
trans2.invoice_number = "Surinder Split Order 2";
// The Payment creation API requires a list of transactions.
List<Transaction> transactions = new List<Transaction>();
transactions.Add(trans1);
transactions.Add(trans2);
#endregion
When carrying this out, I get the following error response from Paypal: "Only single payment transaction currently supported".
It seems Paypal does not allow the seperation of transactions from one request. If this is the case, is there any other way to carry out what I am trying to achieve?
Related
I am trying to get transfer related data from stripe
TransferService service = new TransferService();
TransferListOptions stripeTransferList = new TransferListOptions
{
Destination = accountId,
Limit = 100
};
var list = await service.ListAsync(stripeTransferList);
var finalData = list.FirstOrDefault(x => x.DestinationPaymentId == paymentId);
so when I try to search paymentId from that list I was not able to find any because the page limit is 100 only
Limit = 100
how to fetch all the data and filter from that??
The stripe-dotnet library supports automatic pagination and it's documented here.
This lets you paginate through all the data in your account based on specific criteria that you passed as parameters. For example you can list all Transfer objects made to a specific connected account this way:
TransferService service = new TransferService();
TransferListOptions listOptions = new TransferListOptions
{
Destination = "acct_123456",
Limit = 100
};
foreach (var transfer in service.ListAutoPaging(listOptions)) {
// Do something with this Transfer
}
Now, this allows you to iterate over every Transfer but if you have a lot of data this could be quite slow. An alternative would be to start from the charge id, the py_123456 that you have from your connected account. If you know which account this charge was created on, you can fetch it directly via the API. This is done using the Retrieve Charge API along with passing the connected account id as documented here.
The Charge resource has the source_transfer property which is the id of the Transfer (tr_123) from the platform that created this charge. You can also use the Expand feature, which lets you fetch the entire Transfer object back to get some detailed information about it.
The code would look like this
var transferOptions = new TransferGetOptions{};
transferOptions.AddExpand("source_transfer");
var requestOptions = new RequestOptions();
requestOptions.StripeAccount = "acct_12345";
TransferService service = new TransferService();
Charge charge = service.Get(transferOptions, requestOptions);
// Access information about the charge or the associated transfer
var transferId = charge.SourceTransfer.Id;
var transferAmount = charge.SourceTransfer.TransferData.Amount;
Trying to get my head around how CyberSource works..
In CyberSource, the Secure Acceptance Flexible Token API can be used to tokenize a credit card on client-side for processing for future processing.
That part is easy enough. There's plenty of documentation and a few packages that do it.
How do I then use that token to create a charge with the CyberSource Payments API ?
All examples i've found show how to tokenize the card on client, and how to charge an untokenized card (i.e. card data is sent to server) but I can't find an example that show how to use the Flexible token to create a charge (or pre-auth).
In most other gateways like Stripe, it's pretty clear in documentations but CyberSource doesn't seem to provide that.
Am I missing something?
I'm using Node but happy with solutions in other languages.
https://developer.cybersource.com/api-reference-assets/index.html#flex
https://developer.visa.com/capabilities/cybersource/reference
Ok, thanks to #rhldr for pointing out to this particular documentation.
The answer is in this page https://developer.cybersource.com/api/developer-guides/dita-flex/SAFlexibleToken/FlexMicroform/GetStarted.html under "Using the Token".
Note: Some of the other documentations (not sure why they have some many variations of the docs), like this one don't mention this at all.
See the RESTPaymentAPI section. It needs to be provided as a customerId field.
"paymentInformation": {
"customer": {
"customerId": "7500BB199B4270EFE05340588D0AFCAD"
}
}
Here's a minimal example of how it can be implemented on the API side
var cybersourceRestApi = require('cybersource-rest-client');
var configuration = require('./cybersource/config.js');
var configObject = new configuration();
var instance = new cybersourceRestApi.PaymentsApi(configObject);
var clientReferenceInformation = new cybersourceRestApi.Ptsv2paymentsClientReferenceInformation();
clientReferenceInformation.code = 'test_payment';
var processingInformation = new cybersourceRestApi.Ptsv2paymentsProcessingInformation();
processingInformation.commerceIndicator = 'internet';
var amountDetails = new cybersourceRestApi.Ptsv2paymentsOrderInformationAmountDetails();
amountDetails.totalAmount = "100.00";
amountDetails.currency = 'USD';
var orderInformation = new cybersourceRestApi.Ptsv2paymentsOrderInformation();
orderInformation.amountDetails = amountDetails;
var paymentInformation = new cybersourceRestApi.Ptsv2paymentsPaymentInformation();
// THIS IS THE IMPORTANT BIT
var customer = new cybersourceRestApi.Ptsv2paymentsPaymentInformationCustomer()
customer.customerId = token
paymentInformation.customer = customer
var request = new cybersourceRestApi.CreatePaymentRequest();
request.clientReferenceInformation = clientReferenceInformation;
request.processingInformation = processingInformation;
request.orderInformation = orderInformation;
request.paymentInformation = paymentInformation;
if (!authoriseOnly) {
request.processingInformation.capture = true;
}
Code based on the CyberSource nodejs REST samples: https://github.com/CyberSource/cybersource-rest-samples-node
More info. Once you know where to look, it's actually explained in a few places.
Example, go to https://developer.cybersource.com/cybs-dev-api-ref/index.html#payments-process-a-payment, expand the "REQUEST FIELD DESCRIPTION" underneath and go to
customer
.. customerId
Unique identifier for the customer's card
and billing information.
When you use Payment Tokenization or Recurring Billing and you include
this value in your request, many of the fields that are normally
required for an authorization or credit become optional.
NOTE When you use Payment Tokenization or Recurring Billing, the value
for the Customer ID is actually the Cybersource payment token for a
customer. This token stores information such as the consumer’s card
number so it can be applied towards bill payments, recurring payments,
or one-time payments. By using this token in a payment API request,
the merchant doesn't need to pass in data such as the card number or
expiration date in the request itself.
See "Payment Tokenization," page 222, and "Recurring Billing," page
225.
I have built a review app based on Google's "people viewer" template that allows managers to create and edit reviews for their direct reports.
The app contains the directory model as well as three roles: Admins, HR, EndUsers.
The app contains a user settings model that allows to create and store user settings similar to the "people skills" template.
The app contains a review model that will contain the reviews for every employee. As one employee can have several reviews, this will be a one-to-many relation, either linked to directory model or user settings model.
The reviews should be readable by managers chain of manager. For this I have created a server script, assuming that the EmployeeEmail will be additionally stored in the review. But maybe there is a better alternative?
function getDirectReportsChainForUser_(query) {
var userQuery = app.models.Directory.newQuery();
userQuery.filters.PrimaryEmail._equals = query.parameters.PrimaryEmail;
userQuery.prefetch.DirectReports._add();
userQuery.prefetch.DirectReports.DirectReports._add();
var users = userQuery.run();
if (users.length === 0) {
return [];
}
var user = users[0];
var directs = user.DirectReports;
var records = [];
for (var i = 0; i <= directs.length; i++) {
records.push(directs[i].PrimaryEmail);
}
// The following lines are based on the asumption that the EmployeeEmail
// will be stored in the review in case that there is no better alternative.
//The question that then remains is how to recursively add the DirectReports
//of the DirectReports to the array???
var reviewQuery = app.models.Reviews.newQuery();
reviewQuery.filters.EmployeeEmail._in = records;
return reviewQuery.run();
}
The manager should be able to define whether one or more of his deputies can read the reviews for his unit, too. My idea was to solve this issue through a many-to-many relation between the directory and review model, but I am not sure how to implement it?
Furthermore, once a manager or his deputy departures, it should be possible for the Admin to dissolve the connection and to reconnect the reviews to a successor. Therefore I was thinking about integrating a multiselect in the admin page. Would this be feasible?
Here I see at least two distinct questions:
is there better way to associate directory model's record and ordinary data model than just adding primary email field to the data model
Nope, at this time it is not possible to establish relations between data (SQL/Drive Tables) and directory models.
how to recursively get all direct reports for a user
App Maker's Directory Model is a wrapper on top of G Suit Admin SDK's Directory API that exposes just a small subset of its powerful features. When you add Directory Model App Maker automatically plugs in correspondent Apps Script advance service:
Since we already have configured Directory API we can unleash its full power and easily fetch all manger's subordinates with a single call (or multiple if you have a need to support paging). In order to do that we will use Users.List API method with managerId query parameter (the only one that allows us to query all subordinates down the tree). Here are reference for the minimal set of search query parameters quoted from the full search documentation (without those parameters query would not work or wouldn't work in a way we need):
managerId: The ID of a user's manager either directly or up the management chain.
domain: The domain name. Use this field to get fields from only one domain. To return all domains for a customer account, use the customer query parameter instead. Either the customer or the domain parameter must be provided.
viewType: Whether to fetch the administrator-only or domain-wide public view of the user. For more information, see Retrieve a user as a non-administrator (admin_view is default value so we need to override it with domain_view).
query: Query string for searching user fields. For more information on constructing user queries, see Search for Users.
/**
* Fetches all reviews associated with all subordinate employees (both direct
* and indirect reports).
*/
function getAllReportsEmails(managerId) {
var emails = [];
var result = AdminDirectory.Users.list({
domain: 'ENTER HERE YOUR DOMAIN (exapmle.com)',
query: 'managerId=' + managerId,
viewType: 'domain_public',
maxResults: 100
});
if (result.users) {
emails = result.users.map(function (user) {
return user.primaryEmail;
});
}
return emails;
}
/**
* Fetches all reviews associated with all subordinate employees (both direct
* and indirect reports).
*/
function getAllReportsReviewsForManager_(query) {
var userQuery = app.models.Directory.newQuery();
// For better security I would recommend to use
// Session.getActiveUser().getEmail() instead of parameter
// passed from the client.
userQuery.filters.PrimaryEmail._equals = Session.getActiveUser().getEmail();
var users = userQuery.run();
if (users.length === 0) {
return [];
}
var manager = users[0];
var managerId = manager._key;
var allReportsEmails = getAllReportsEmails(managerId);
var reviewQuery = app.models.Reviews.newQuery();
reviewQuery.filters.EmployeeEmail._in = allReportsEmails;
return reviewQuery.run();
}
Pavel, I tried to integrate the ideas you gave me into one server script that returns an array of the manager and his whole subordinate chains (direct reports + indirect reports), so that I can use it whenever needed. I turned into a recursive function to get the direct reports and indirect reports on the next lower level. Is there a way to get the whole chain?
function getSubordinatesChainForUser(query) {
var userQuery = app.models.Directory.newQuery();
userQuery.filters.PrimaryEmail._equals = Session.getActiveUser().getEmail();
userQuery.prefetch.DirectReports._add();
userQuery.prefetch.DirectReports.DirectReports._add();
var users = userQuery.run();
if (users.length === 0) {
return [];
}
var userEmails = users.map(function(manager){
var employeeEmails = manager.DirectReports.map(function(employee){
return employee.PrimaryEmail;
});
return manager.PrimaryEmail + ',' + employeeEmails;
});
return userEmails;
}
Using GoogleAppMaker how to create a data source from google contacts. There is an employee HR example app but I want to similarly manage contacts (add, modify, delete) and use select criteria.
At this time this task is not trivial in App Maker and it is pretty much generic. We can change question wording to CRUD operations with 3rd party datasources. Let's break it into smaller parts and address them separately.
Read/list contacts
This task is relatively easy. You need to use Calculated Model to proxy Apps Scripts Contacts API response. Once you create model with subset of fields from the Contact response you can create datasource for the model and bind it to List or Table widget. You can also try to find some inspiration in Calculated Model Sample.
// Server side script
function getContacts_() {
var contacts = ContactsApp.getContacts();
var records = contacts.map(function(contact) {
var record = app.models.Contact.newRecord();
record.FirstName = contact.getGivenName();
record.LastName = contact.getFamilyName();
var companies = contact.getCompanies();
if (companies.length > 0) {
var company = companies[0];
record.Organization = company.getCompanyName();
record.Title = company.getJobTitle();
}
var emails = contact.getEmails();
if (emails.length > 0) {
record.Email = emails[0].getAddress();
}
var phones = contact.getPhones();
if (phones.length > 0) {
record.Phone = phones[0].getPhoneNumber();
}
return record;
});
return records;
}
Create/Update/Delete
Since Calculated Models have some limitations, we need to turn on our imagination to create, update and delete records from their datasources. The basic strategy will be calling server side scripts for CUD operations in response to user actions on client side. To get user's input from UI we will need to utilize page's Custom Properties, one property for each Contact field:
Here are some snippets that should explain the idea
Create
// Client script
function onSubmitContactClick(submitButton) {
var props = submitButton.root.properties;
var contact = {
FirstName: props.FirstName,
LastName: props.LastName,
Organization: props.Organization,
...
};
google.script.run
.withSuccessHandler(function() {
// Most likely we'll need to navigate user back to the
// page with contacts list and reload its datasource
// to reflect recent changes, because our `CUD` operations
// are fully detached from the list datasource
app.showPage(app.pages.Contacts);
app.datasources.Contacts.load();
})
.withFailureHandler(function() {
// TODO: Handle error
})
.createContact(contact);
}
// Server script
function createContact(contactDraft) {
var contact = ContactsApp.createContact(contactDraft.FirsName,
contactDraft.LastName,
contactDraft.Email);
contact.addCompany(contactDraft.Organization, contactDraft.Title);
contact.addPhone(ContactsApp.Field.WORK_PHONE, contactDraft.Phone);
}
Update
Idea to update contact records will be very similar to the new contact creation flow, so I skip it for now.
Delete
Assuming that delete button is located inside contacts table row.
// Client script
function onDeleteContactClick(deleteButton) {
var email = deleteButton.datasource.item.Email;
google.script.run
.withSuccessHandler(function() {
// To update contacts list we can either reload the entire
// datasource or explicitly remove deleted item on the client.
// Second option will work way faster.
var contactIndex = deleteButton.parent.childIndex;
app.datasources.Contacts.items.splice(contactIndex, 1);
})
.withFailureHandler(function() {
// TODO: Handle error
})
.deleteContact(contact);
}
// Server script
function deleteContact(email) {
var contact = ContactsApp.getContact(email);
ContactsApp.deleteContact(contact);
}
I'm trying to call MailChimp Subscribe with MailChimp.Net NuGet package to add user to list in MailChimp.
The request is performed with success as far as I can see from MailChimp dashboard but the user is not subscribed in the list.
Did anyone faced such issue?
var myMergeVars = new MergeVar();
myMergeVars.Add("FNAME", "Testy");
myMergeVars.Add("LNAME", "Testerson");
var mc = new MailChimpManager("MYKEY");
// Create the email parameter
var email = new EmailParameter()
{
Email = "test.spektor#gmail.com"
};
EmailParameter results = mc.Subscribe("LISTID", email);
The request should be made with doubleOptIn: false option, otherwise user will receive an email with confirmation on his email box.
var result = mc.Subscribe(listId, emailParameter, myMergeVars, doubleOptIn: false, updateExisting: true);