How to save card after payment in stripe? - asp.net-core

I want to save a card for next payments in my app, but always get the same exception : "Stripe.StripeException: 'The provided PaymentMethod was previously used with a PaymentIntent without Customer attachment, shared with a connected account without Customer attachment, or was detached from a Customer. It may not be used again. To use a PaymentMethod multiple times, you must attach it to a Customer first.'
"
I don't have any clue, how to solve it.
Here is my Controller.cs:
public class PaymentController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Processing()
{
var service = new PaymentMethodService();
var obj=service.Get("pm_1ICLE7GcqJgpxMZpnTbfS7Jw");
var paymentIntents = new PaymentIntentService();
var paymentIntent = paymentIntents.Create(new PaymentIntentCreateOptions
{
Amount = 2000,
Currency = "usd",
Customer = "cus_Ing6wBxNYVdB44",
ReceiptEmail = "eman29#jdecorz.com",
PaymentMethod = obj.Id,
Confirm = true,
OffSession = true
});//here exception is thrown
return Json(new { clientSecret = paymentIntent.ClientSecret });
}
}
My client.js code:
var stripe = Stripe("pk_test_51IBEAOGcqJgpxMZpvVKN2j9K7RJpzazfnG4u0relgSXiVBtNDd7nGgxBmX8BNCvuNerv1jnf0UVL5Uz8ODeJ7wvI00ruu2ByVM");
// Disable the button until we have Stripe set up on the page
document.querySelector("button").disabled = true;
fetch("/Payment/Processing", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(purchase)
})
.then(function (result) {
return result.json();
})
.then(function (data) {
var elements = stripe.elements();
var style = {
base: {
color: "#32325d",
fontFamily: 'Arial, sans-serif',
fontSmoothing: "antialiased",
fontSize: "16px",
"::placeholder": {
color: "#32325d"
}
},
invalid: {
fontFamily: 'Arial, sans-serif',
color: "#fa755a",
iconColor: "#fa755a"
}
};
var card = elements.create("card", { style: style });
// Stripe injects an iframe into the DOM
card.mount("#card-element");
card.on("change", function (event) {
// Disable the Pay button if there are no card details in the Element
document.querySelector("button").disabled = event.empty;
document.querySelector("#card-error").textContent = event.error ? event.error.message : "";
});
var form = document.getElementById("payment-form");
form.addEventListener("submit", function (event) {
event.preventDefault();
// Complete payment when the submit button is clicked
payWithCard(stripe, card, data.clientSecret);
});
});
// Calls stripe.confirmCardPayment
// If the card requires authentication Stripe shows a pop-up modal to
// prompt the user to enter authentication details without leaving your page.
var payWithCard = function (stripe, card, clientSecret) {
loading(true);
stripe
.confirmCardPayment(clientSecret, {
payment_method: {
card: card
}
})
.then(function (result) {
if (result.error) {
// Show error to your customer
showError(result.error.message);
} else {
// The payment succeeded!
orderComplete(result.paymentIntent.id);
}
});
};
/* ------- UI helpers ------- */
// Shows a success message when the payment is complete
var orderComplete = function (paymentIntentId) {
loading(false);
document
.querySelector(".result-message a")
.setAttribute(
"href",
"https://dashboard.stripe.com/test/payments/" + paymentIntentId
);
document.querySelector(".result-message").classList.remove("hidden");
document.querySelector("button").disabled = true;
};
// Show the customer the error from Stripe if their card fails to charge
var showError = function (errorMsgText) {
loading(false);
var errorMsg = document.querySelector("#card-error");
errorMsg.textContent = errorMsgText;
setTimeout(function () {
errorMsg.textContent = "";
}, 4000);
};
// Show a spinner on payment submission
var loading = function (isLoading) {
if (isLoading) {
// Disable the button and show a spinner
document.querySelector("button").disabled = true;
document.querySelector("#spinner").classList.remove("hidden");
document.querySelector("#button-text").classList.add("hidden");
} else {
document.querySelector("button").disabled = false;
document.querySelector("#spinner").classList.add("hidden");
document.querySelector("#button-text").classList.remove("hidden");
}
};
I try to use samples from https://stripe.com/docs/payments/save-during-payment and https://stripe.com/docs/payments/save-and-reuse, but can't understand, what I do wrongly
I know this is the dumb question, but it is my first expierence with Stripe and I can't to find any solution for this problem.
Thank you in advance!

When re-using a PaymentMethod for a Customer, it must be attached to them. There is a few ways to go about that. For example, one option is to create a payment method and then to call attach in the backend [1]. The other option is to collect card information using Stripe.js and Elements and to "setup future usage", this will automatically attach the card to the customer [2].
One thing to note, if your code uses confirmCardPayment() [3], that would normally be an "on-session" payment as the user is actively confirming the charge. [4]
[1] https://stripe.com/docs/api/payment_methods/attach
[2] https://stripe.com/docs/js/payment_intents/confirm_card_payment#stripe_confirm_card_payment-data-setup_future_usage
[3] https://stripe.com/docs/js/payment_intents/confirm_card_payment
[4] https://stripe.com/docs/api/payment_intents/create#create_payment_intent-off_session

Related

Shopify custom gift boxes inventory update

I created a custom gift box creator for a client. The current approach is that I created a "Gift Box" product and add the selected items as attributes on the cart item. This is all done in the theme's JS code but the problem I'm facing is that because I'm not actually adding the underlying items to the cart inventory is not updating.
Is there a way to not show the underlying items in the cart but have checkout update their inventory counts?
What if you had a microservice which responded to order/created and edited the order to include the items # $0?
You could use the steps outlined in this official Shopify tutorial on their GraphQL feature, and implemented in node:
import Shopify from 'shopify-api-node'; // https://github.com/MONEI/Shopify-api-node
shopify = new Shopify({/* your app credentials */});
// assume this is the HTTP endpoint w/ a JSON middleware
function(req, res) {
const order = req.body;
const giftBasket = order.line_items.find(li => li.id === 1234); // 1234, or whatever the product ID of your gift basket is.
const realItemSKUs = giftBasket.properties.filter((lineItemProp) => {
// assuming you use a product1: sku, product2: sku line item attribute name/value.
// but adjust to your needs.
return lineItemProp.name.match('product\d');
}).map((lineItemProp) => lineItemProp.value);
function createVariantQuery(sku) {
return `product${id}: {
productVariants(query: "sku:${sku}") { id }
}`;
}
const query = realItemSKUs.map((sku) => createVariantQuery(sku)).join('\n')
const variantIds = await shopify.graphql(`{${query}}`)
.then((variants) => Object.values(variants).map((variant) => variant.id));
const orderEditRes = await shopify.graphql(`mutation beginEdit{
orderEditBegin(id: "gid://shopify/Order/1234"){
calculatedOrder{
id
}
}
}`);
const calcLineItems = await Promise.all(variantIds.map(async (id) =>
shopify.graphql(`mutation addVariantToOrder{
orderEditAddVariant(id: "gid://shopify/CalculatedOrder/${orderEditRes.calculatedOrder}", variantId: "${id}", quantity: 1) {
calculatedOrder {
id
addedLineItems(first:5) {
edges {
node {
id
quantity
}
}
}
}
userErrors {
field
message
}
}
}`);
));
await Promise.all(calcLineItems.map(async (calcLineItem =>
shopify.graphql(`addDiscount {
orderEditAddLineItemDiscount(id: "${calculatedOrder.id}", lineItemId: "${calcLineItem.id}", discount: {percentValue: 100, description: "Giftbasket"}) {
calculatedOrder {
id
addedLineItems(first:5) {
edges {
node {
id
quantity
}
}
}
}
userErrors {
message
}
}
}`
);
return shopify.graphql(`mutation commitEdit {
orderEditCommit(id: "${calculatedOrder}", notifyCustomer: false, staffNote: "Auto giftbasket updated") {
order {
id
}
userErrors {
field
message
}
}
}`);
};

Angularjs - what are the possible reasons for duplicate records being inserted by the following code?

The following code is called on the click of a button
$scope.someFunction = function () {
$scope.submitting = true; // the button is disabled if submitting is true
var query = { query: { id: $scope.employeeID } };
// this api call inserts a record in a table
httpFactory.patch("/someURL", query).then(function (data) {
$scope.submitting = false;
if (data.error) {
// display error message
}
else {
// display success message
}
$scope.submitting = false;
}, function () {
$scope.submitting = false;
});
};
can duplicate records be inserted from the call above if a user has poor connectivity or if the server is slow and the request is not completed and soon another same request is received?
If so.. could any one please suggest a suitable way to handle this?

Firebase make user object from auth data

So I'm using Angularfire in an ionic app and trying to figure out how to make a user object that is associated with the auth data from an Auth $createUser call. My first try had the auth call and the user got authenticated, then a user object was made and pushed into a $firebaseArray which works fine, but I don't know how to grab the current user after they are logged in to update, destory, or do anything with that users data. I have made it work with looping through the users array and matching the uid from the user array item and the auth.uid item which was set to be the same in the user array object creation. This seems really ineffecient to loop over if there is a large user array and it needs to be done on multiple pages.
My current attempt is using a different method like so:
angular.module('haulya.main')
.controller('RegisterController', ['Auth', '$scope', 'User', '$ionicPlatform', '$cordovaCamera','CurrentUserService',
function(Auth, $scope, User, $ionicPlatform, $cordovaCamera, CurrentUserService) {
//scope variable for controller
$scope.user = {};
console.log(User);
$scope.createUser = function(isValid) {
var userModel;
$scope.submitted = true;
//messages for successful or failed user creation
$scope.user.message = null;
$scope.user.error = null;
//if form is filled out valid
if(isValid) {
//Create user with email and password firebase Auth method
Auth.$createUser({
email: $scope.user.email,
password: $scope.user.password
})
.then(function(userData) {
userModel = {
uid: userData.uid,
photo: $scope.user.photo || null,
firstName: $scope.user.firstName,
lastName: $scope.user.lastName,
email: $scope.user.email,
cell: $scope.user.cell,
dob: $scope.user.dob.toString(),
city: $scope.user.city,
state: $scope.user.state,
zip: $scope.user.zip
}
// add new user to profiles array
User.create(userModel).then(function(user) {
$scope.sharedUser = User.get(user.path.o[1]);
});
$scope.user.message = "User created for email: " + $scope.user.email;
})
.catch(function(error) {
//set error messages contextually
if(error.code == 'INVALID_EMAIL') {
$scope.user.error = "Invalid Email";
}
else if(error.code == 'EMAIL_TAKEN'){
$scope.user.error = "Email already in use, if you think this is an error contact an administrator";
}
else {
$scope.user.error = "Fill in all required fields";
}
});
}
};
//Get profile pic from camera, or photo library
$scope.getPhoto = function(type) {
$ionicPlatform.ready(function() {
//options for images quality/type/size/dimensions
var options = {
quality: 65,
destinationType: Camera.DestinationType.DATA_URL,
sourceType: Camera.PictureSourceType[type.toUpperCase()],
allowEdit: true,
encodingType: Camera.EncodingType.JPEG,
targetWidth: 100,
targetHeight: 100,
popoverOptions: CameraPopoverOptions,
saveToPhotoAlbum: false
};
//get image function using cordova-plugin-camera
$cordovaCamera.getPicture(options)
.then(function(photo) {
$scope.user.photo = photo;
}, function(err) {
console.log(err);
});
});
};
}]);
And here's the service the controller is using:
angular
.module('haulya.main')
.factory('User', function($firebaseArray) {
var ref = new Firebase('https://haulya.firebaseio.com');
var users = $firebaseArray(ref.child('profiles'));
var User = {
all: users,
create: function(user) {
return users.$add(user);
},
get: function(userId) {
return $firebaseArray(ref.child('profiles').child(userId));
},
delete: function(user) {
return users.$remove(user);
}
};
return User;
});
This also works, but again I don't have a solid reference to the currently logged in users object data from the array. The objects id is only stored on the controllers scope.
I looked through other posts, but they were all using older versions of firebase with deprecated methods.
If you're storing items that have a "natural key", it is best to store them under that key. For users this would be the uid.
So instead of storing them with $add(), store them with child().set().
create: function(user) {
var userRef = users.$ref().child(user.uid);
userRef.set(user);
return $firebaseObject(userRef);
}
You'll note that I'm using non-AngularFire methods child() and set(). AngularFire is built on top of Firebase's regular JavaScript SDK, so they interoperate nicely. The advantage of this is that you can use all the power of the Firebase JavaScript SDK and only use AngularFire for what it's best at: binding things to Angular's $scope.
Storing user data is explained in Firebase's guide for JavaScript. We store them under their uid there too instead of using push(), which is what $add() calls behind the scenes.

How to pass value from Fb.api to Fb.ui

I need to exclude friend on invite dialog request and function looklike
duplicate:function(){
var responsive = '';
FB.api(
{
method: 'fql.query',
query:'SELECT uid,name FROM user WHERE uid IN \n\
(SELECT uid2 FROM friend WHERE uid1 = me()) AND is_app_user = 1 '
},
function(response) {
responsive = response;}
);
return responsive;
},
onInviteClick: function(responsive) {
FB.ui( {
method: 'apprequests',
title: 'Popsecret ป็อบคอร์นแสนอร่อย',
exclude_ids: responsive
message: 'ชวนคุณกดไลท์เพื่อลุ้นรับไอแพดเเละของรางวัลอีกมากมาย',
max_recipients: 15
} , function(response) {
if (response !== null) {
$.post(Site.inviteCallbackURL, response, function(res) {
});
}
});
and I can't pass data from duplicate to onInviteClick
Move var responsive = ''; from function into global scope. Then you can access it from all other functions. Keep in mind, that the API call is asynchronous, so you need to wait for the completion of the request (jQuery Deferreds might help you there)

Transition with keepScrollPosition and navigateBack

We are using Durandal for our SPA application and came to a, in my opinion, common use case. We have two pages: one page is a list of entities (with filters, sorting, virtual scroll) and another is detail preview of an entity. So, user is on list page and set a filter and a list of results comes out. After scrolling a little bit down user notice an entity which he/she would like to see details for. So clicking on a proper link user is navigated to details preview page.
After "work finished" on preview page user click back button (in app itself or browser) and he/she is back on the list page. However, default 'entrance' transition scroll the page to the top and not to the position on list where user pressed preview. So in order to 'read' list further user have to scroll down where he/she was before pressing preview.
So I started to create new transition which will for certain pages (like list-search pages) keep the scroll position and for other pages (like preview or edit pages) scroll to top on transition complete. And this was easy to do however, I was surprised when I noticed that there are strange behavior on preview pages when I hit navigateBack 'button'. My already long story short, after investigation I found out that windows.history.back is completing earlier then the transition is made and this cause that preview pages are scrolled automatically down to position of previous (list) page when back button is hit. This scrolling have a very unpleasant effect on UI not mentioning that it is 'total catastrophe' for my transition.
Any idea or suggestion what could I do in this case?
Here is the code of transition. It is just a working copy not finished yet as far as I have this problem.
define(['../system'], function (system) {
var fadeOutDuration = 100;
var scrollPositions = new Array();
var getScrollObjectFor = function (node) {
var elemObjs = scrollPositions.filter(function (ele) {
return ele.element === node;
});
if (elemObjs.length > 0)
return elemObjs[0];
else
return null;
};
var addScrollPositionFor = function (node) {
var elemObj = getScrollObjectFor(node);
if (elemObj) {
elemObj.scrollPosition = $(document).scrollTop();
}
else {
scrollPositions.push({element: node, scrollPosition: $(document).scrollTop()});
}
};
var scrollTransition = function (parent, newChild, settings) {
return system.defer(function (dfd) {
function endTransition() {
dfd.resolve();
}
function scrollIfNeeded() {
var elemObj = getScrollObjectFor(newChild);
if (elemObj)
{
$(document).scrollTop(elemObj.scrollPosition);
}
else {
$(document).scrollTop(0);
}
}
if (!newChild) {
if (settings.activeView) {
addScrollPositionFor(settings.activeView);
$(settings.activeView).fadeOut(fadeOutDuration, function () {
if (!settings.cacheViews) {
ko.virtualElements.emptyNode(parent);
}
endTransition();
});
} else {
if (!settings.cacheViews) {
ko.virtualElements.emptyNode(parent);
}
endTransition();
}
} else {
var $previousView = $(settings.activeView);
var duration = settings.duration || 500;
var fadeOnly = !!settings.fadeOnly;
function startTransition() {
if (settings.cacheViews) {
if (settings.composingNewView) {
ko.virtualElements.prepend(parent, newChild);
}
} else {
ko.virtualElements.emptyNode(parent);
ko.virtualElements.prepend(parent, newChild);
}
var startValues = {
marginLeft: fadeOnly ? '0' : '20px',
marginRight: fadeOnly ? '0' : '-20px',
opacity: 0,
display: 'block'
};
var endValues = {
marginRight: 0,
marginLeft: 0,
opacity: 1
};
$(newChild).css(startValues);
var animateOptions = {
duration: duration,
easing : 'swing',
complete: endTransition,
done: scrollIfNeeded
};
$(newChild).animate(endValues, animateOptions);
}
if ($previousView.length) {
addScrollPositionFor(settings.activeView);
$previousView.fadeOut(fadeOutDuration, startTransition);
} else {
startTransition();
}
}
}).promise();
};
return scrollTransition;
});
A simpler approach could be to store the scroll position when the module deactivates and restore the scroll on viewAttached.
You could store the positions in some global app variable:
app.scrollPositions = app.scrollPositions || {};
app.scrollPositions[system.getModuleId(this)] = theCurrentScrollPosition;