I'm writing a service which allows users to sign up for classes, at the time of sign up the class instructor may not be known, but I need to create a pending charge to assure that the instructor will be paid. Later I want to update the pending and set a finalization date, e.g update the instructors account Id and 24 hours after the class ends pay the instructor
I'm trying to understand the work flow of how to do it, but the API Doc's don't seem to be helpful.
I have a service to create Payment Intents like so:
var paymentIntentService = new PaymentIntentService();
var paymentIntentTransferDataOptions = new PaymentIntentTransferDataOptions();
var options = new PaymentIntentCreateOptions
{
Amount = paymentIntentTransactionOptions.AmountInCents,
Currency = DEFAULT_TRANSACTION_CURRENCY,
ApplicationFeeAmount = this.CalculateApplicationFee(paymentIntentTransactionOptions),
ReceiptEmail = "", // TODO Obtain this from the Logged in user
PaymentMethodTypes = new List<string> { "card" },
};
var requestOptions = new RequestOptions();
requestOptions.IdempotencyKey = INTENT_IDEM_PREFIX + paymentIntentTransactionOptions.TransactionId;
var paymentIntent = await paymentIntentService.CreateAsync(options, requestOptions);
I would like to create a pending charge with no destination at first and before completion I will update the user to send it to.
The update process I would assume to just, call Get on the PaymentIntent and Update the Sender.
My confusion lays around 3 areas.
How does the API Know what user I'm allowed to send on behalf of a user?
e.g I just provide an accountId. Does the api have full control of other accounts after registration?
How can I Create a pending charge, to assure the instructor gets paid, does that happen when I create the payment intent?
How do I finalize the transaction (Now, or preferable with a future date)
I recently created an entire sharing economy platform with Stripe Connect, so I'm fairly familiar with it.
Charging the User: You'll need to create a regular charge for the user after they schedule the class. Read this on creating charges. This will charge the user $X and hold it in the platforms account without yet transferring it.
Paying the Instructor: You'll need to create a separate transfer using source_transaction to send the money to the instructor's account. source_transaction will basically just make the transfer from the charge you made earlier from the customer. You can read more about that here.
Regarding holding the funds for 24hrs, if you use source_transaction, the money won't become available for payout until after 2 days due to processing. You can read more about that here. If you want it to become available faster, you have 2 options. Either you can enable Instant Payout (which I don't recommend) or you can have a reserve balance in your platform stripe balance to cover the one-day layover and then you can make the transfers without source_transaction.
Okay, so after asking a few more questions and reading api docs. There are a few ways of doing this. #nachshonf answer will get the job done. However if I use transfers and seperate charges, The platform will be responsible for the Stripe fees and refund.
Instead I've come up with a more complex way of doing this, but will assure less of a headache for me in the long run. Basically I create a hold via the platform, and then when the instructor is resolved will request to issue another charge for the instructor. This way all disputes go through the instructor.
First off I'll create a hold via the platform, This is pretty simple
public Task<PaymentIntent>CreatePlatformHoldAsync(long amountInCents, string customerId,
string paymentMethodId, string idem = null, string currency = DEFAULT_TRANSACTION_CURRENCY)
{
var paymentIntentService = new PaymentIntentService();
var options = new PaymentIntentCreateOptions
{
Amount = amountInCents,
Currency = currency,
//ReceiptEmail = "", // TODO Obtain this from the Logged in user
PaymentMethodTypes = new List<string> { "card" },
CustomerId = customerId,
PaymentMethodId = paymentMethodId,
CaptureMethod = "manual",
};
var requestOptions = new RequestOptions { IdempotencyKey = idem };
return paymentIntentService.CreateAsync(options, requestOptions);
}
/// <summary>
/// Confirm a payment intent, on the platform or sellerAccount
/// </summary>
/// <param name="sellerStripeAccountId">optional, omit for the platform confirm</param>
public Task<PaymentIntent> ConfirmPaymentAsync(string paymentIntentId,
string sellerStripeAccountId = null)
{
var paymentIntentService = new PaymentIntentService();
var paymentIntentConfirmOptions = new PaymentIntentConfirmOptions();
var options = new RequestOptions
{
StripeAccount = sellerStripeAccountId
};
return paymentIntentService.ConfirmAsync(paymentIntentId,
paymentIntentConfirmOptions, options);
}
First create a hold, Then confirm it to authorize the charges. Then I'll create another charge for the instructor
public Task<PaymentIntent> CreateDestinationChargeAsync(long amountInCents, long applicationFeeInCents,
string paymentMethodId, string destinationAccountId, string idem = null,
string currency = DEFAULT_TRANSACTION_CURRENCY)
{
var paymentIntentService = new PaymentIntentService();
var options = new PaymentIntentCreateOptions
{
Amount = amountInCents,
Currency = currency,
ApplicationFeeAmount = applicationFeeInCents,
//ReceiptEmail = "", // TODO Obtain this from the Logged in user
PaymentMethodTypes = new List<string> { "card" },
PaymentMethodId = paymentMethodId,
};
var requestOptions = new RequestOptions
{
IdempotencyKey = idem,
StripeAccount = destinationAccountId
};
return paymentIntentService.CreateAsync(options, requestOptions);
}
After this is paid you can cancel the platform hold or wait 7 days for the hold to age away.
Edit For Usages
public async Task<Customer> CreateCustomerAsync(string email, string sourceToken)
{
var options = new CustomerCreateOptions
{
Email = email, // "paying.user#example.com",
Source = sourceToken,
};
var service = new CustomerService();
return await service.CreateAsync(options);
}
/// <summary>
/// Creates a payment method for a customer on the sellers stripe account
/// </summary>
/// <returns></returns>
public async Task<PaymentMethod> CreatePaymentMethodAsync(string customerId, string paymentMethodId,
string stripeConnectAccountId)
{
var paymentMethodService = new PaymentMethodService();
var paymentMethodOptions = new PaymentMethodCreateOptions
{
CustomerId = customerId,
PaymentMethodId = paymentMethodId
};
var requestOptions = new RequestOptions()
{
StripeAccount = stripeConnectAccountId
};
return await paymentMethodService.CreateAsync(paymentMethodOptions, requestOptions);
}
Usage:
//set destination here
var destinationAccountId = "";
var configuration = this.GetConfiguration();
StripeConfiguration.ApiKey = configuration["Stripe:ClientSecret"];
//This is the name of the service which I define the methods above
var stripeService = new StripeConnectService(configuration);
//"tok_mastercard" is a test value to represent a paymentToken
var customer = await stripeService.CreateCustomerAsync("CustomerEmail#gmail.com", "tok_mastercard");
var sharedPaymentMethod = await stripeService.CreatePaymentMethodAsync(customer.Id,
customer.DefaultSourceId, destinationAccount.AccountId);
var paymentIntent = await stripeService.CreateDestinationChargeAsync(1000, 100,
sharedPaymentMethod.Id, destinationAccountId);
await stripeService.ConfirmPaymentAsync(paymentIntent.Id, destinationAccountId);
These are examples and are not meant for production I'm only using them to test the flow.
Note: the payment token represents a customer. I haven't implemented a way to obtain the payment token but it looks like you will need stripe.js so they can enter there card information to create a token.
Related
I am working on a C# application where the app users will purchase a subscription for their clients. Since they will buy multiple subscriptions, I need to add a description (client name) to a product so it shows up in the billing portal along with the price. My current checkout code works fine and the relevant part is here:
var options = new Stripe.Checkout.SessionCreateOptions
{
LineItems = new List<SessionLineItemOptions>
{
new SessionLineItemOptions
{
Price = priceId,
Quantity = 1
},
},
PaymentMethodTypes = new List<string>
{
"card",
},
Mode = "subscription",
SuccessUrl = string.Concat("https://", domain, "/success/", hhid, "?session_id={CHECKOUT_SESSION_ID}"),
CancelUrl = string.Concat("https://", domain),
Customer = customerId,
SubscriptionData = new SessionSubscriptionDataOptions()
{
Metadata = new Dictionary<string, string>()
{
{ "userId", userId.ToString() }
}
}
};
if (!string.IsNullOrEmpty(user.PaymentCustomerId))
options.Customer = user.PaymentCustomerId;
var service = new Stripe.Checkout.SessionService();
Stripe.Checkout.Session session = await service.CreateAsync(options);
Response.Headers.Add("Location", session.Url);
I contacted Stripe support and their response was
In this particular case we do not have an option that allow you to add a description, but you are able to add the name with price_data parameter into the Checkout, Invoice Items, and Subscription Schedule APIs.
https://stripe.com/docs/api/subscription_items/create
https://stripe.com/docs/billing/prices-guide
They provided links to the two articles which I have read and re-read and don't understand how to implement it. If anyone can help it would be greatly appreciated.
Unfortunately, the Billing Portal does not show a description for the Product.
If you want to use the Billing Portal, then you would need to specify the client name in the name for the Product e.g. [client name] Product #1, and this would require you to create a new Product and Price for every client.
You can do it in the Checkout Session creation :
https://stripe.com/docs/api/checkout/sessions/create#create_checkout_session-line_items-price_data-product_data-name
https://stripe.com/docs/api/checkout/sessions/create#create_checkout_session-line_items-price_data
Or, create the Product and Price separately :
https://stripe.com/docs/api/products/create
https://stripe.com/docs/api/prices/create
I'm trying to get the billing address from Stripe Checkout from a Webhook call.
What I'm trying to achieve is to get the information from the form in the yellow rectangle.
This is my Checkout configuration :
var options = new SessionCreateOptions()
{
CustomerEmail = user.Email,
BillingAddressCollection = "required",
ShippingAddressCollection = new SessionShippingAddressCollectionOptions
{
AllowedCountries = new List<string>
{
"FR",
},
},
PaymentMethodTypes = new List<string>() {
result.Payment.Type
},
LineItems = new List<SessionLineItemOptions>{
new SessionLineItemOptions
{
PriceData = new SessionLineItemPriceDataOptions
{
UnitAmountDecimal = result.Payment.Amount * 100,
Currency = result.Payment.Currency,
ProductData = new SessionLineItemPriceDataProductDataOptions
{
Name = _stringLocalizer.GetString("StripeProductLabel"),
},
},
Quantity = 1,
},
},
Mode = result.Payment.Mode,
SuccessUrl = $"{request.Scheme}://{request.Host}" + "/payment/complete",
CancelUrl = $"{request.Scheme}://{request.Host}" + "/payment/cancel",
Metadata = new Dictionary<string, string>()
{
{ Constants.StripeMetaDataOrderId, result.Id }
}
};
and when I receive the session objet in the completed event : session = stripeEvent.Data.Object as Stripe.Checkout.Session;
I can't get the information because the paymentIntent object is null ( information from : Retrieve Billing Address from Stripe checkout session? ).
This is an important feature from Sripe because the application is a B2B application to help professionals to create orders for their B2C business. It will avoid making custom code from something that exits in Stripe API :)
Thanks in advance
The answer you linked to is the correct way to get this information, from the payment_method on the payment_intent. I'm not sure how/why your payment_intent value would not be populated, as my testing indicates this to be initialized upon creating the session, even if I never redirect to it.
Are you certain you're creating a mode=payment session? I see that in the code you shared, but things will change a bit if you're actually doing setup or subscription mode.
I am attempting to change from a paid subscription to a $0 subscription and immediately issue the pro-rated refund.
I was able to get the pro-rated amount using this Q/A: How do you preview a proration using Stripe.NET?. And when I actually update the subscription with the new plan id, the upcoming invoice shows the pro-rated amount but no actual refund is issued. And that makes sense for paid subscriptions, but I need to refund.
Is there a way to execute the refund as part of the Stripe subscription update request? Or do I need to make a separate request to do the refund?
Here's the code I use to update the subscription:
var items = new List<SubscriptionItemOptions> {
new SubscriptionItemOptions {
Id = subscription.Items.Data[0].Id,
Plan = request.NewPlanId,
Deleted = false
}
};
var options = new SubscriptionUpdateOptions
{
Items = items,
ProrationDate = request.ProrationDate,
ProrationBehavior = "create_prorations" // none, create_prorations
};
var updatedSubscription = await subscriptionService.UpdateAsync(subscription.Id, options);
Context:
I am creating new MS Teams team using MS Graph API in C#
My code:
var newTeam = new Team()
{
DisplayName = model.DisplayName,
Description = model.Description,
AdditionalData = new Dictionary<string, object>
{
["template#odata.bind"] = $"{graph.BaseUrl}/teamsTemplates('standard')",
["members"] = owners.ToArray()
}
};
var team = await graph.Teams.Request().AddAsync(newTeam);
Problem:
The team is created fine but I can't get it's id. Return type of AddAsync method is Task<Team> but it always returns null.
I have checked server response using Fiddler and found out that the id of created team is returned in response headers.
Content-Location: /teams('cbf27e30-658b-4021-a8c6-4002b9adaf41')
Unfortunately I don't know how to access this information.
Luckily this is quite easy to achieve by using the requests base class BaseRequest and sending it yourself. With the received HttpResponseMessage you get the headers as documented here which contains the id of your new team.
The code also includes how to wait for the team creation to finish using a busy wait – which is not considered as best practice, but makes the sample easier. A better approach would be to store the team id and periodically query the creation status.
var newTeam = new Team()
{
DisplayName = model.DisplayName,
Description = model.Description,
AdditionalData = new Dictionary<string, object>
{
["template#odata.bind"] = $"{graph.BaseUrl}/teamsTemplates('standard')",
["members"] = owners.ToArray()
}
};
// we cannot use 'await client.Teams.Request().AddAsync(newTeam)'
// as we do NOT get the team ID back (object is always null) :(
BaseRequest request = (BaseRequest)graph.Teams.Request();
request.ContentType = "application/json";
request.Method = "POST";
string location;
using (HttpResponseMessage response = await request.SendRequestAsync(newTeam, CancellationToken.None))
location = response.Headers.Location.ToString();
// looks like: /teams('7070b1fd-1f14-4a06-8617-254724d63cde')/operations('c7c34e52-7ebf-4038-b306-f5af2d9891ac')
// but is documented as: /teams/7070b1fd-1f14-4a06-8617-254724d63cde/operations/c7c34e52-7ebf-4038-b306-f5af2d9891ac
// -> this split supports both of them
string[] locationParts = location.Split(new[] { '\'', '/', '(', ')' }, StringSplitOptions.RemoveEmptyEntries);
string teamId = locationParts[1];
string operationId = locationParts[3];
// before querying the first time we must wait some secs, else we get a 404
int delayInMilliseconds = 5_000;
while (true)
{
await Task.Delay(delayInMilliseconds);
// lets see how far the teams creation process is
TeamsAsyncOperation operation = await graph.Teams[teamId].Operations[operationId].Request().GetAsync();
if (operation.Status == TeamsAsyncOperationStatus.Succeeded)
break;
if (operation.Status == TeamsAsyncOperationStatus.Failed)
throw new Exception($"Failed to create team '{newTeam.DisplayName}': {operation.Error.Message} ({operation.Error.Code})");
// according to the docs, we should wait > 30 secs between calls
// https://learn.microsoft.com/en-us/graph/api/resources/teamsasyncoperation?view=graph-rest-1.0
delayInMilliseconds = 30_000;
}
// finally, do something with your team...
You will get your teamId by calling the GET joined Teams, it will get the team id ,name and description
I first send a proactive message to the user via sms channel inside OAuthCallback method
var connector = new ConnectorClient();
Message message = new Message();
message.From = new ChannelAccount { Id = Constants.botId, Address = "+12312311", ChannelId = "sms", IsBot = true };
message.To = new ChannelAccount { Id = newUserId, Address = "+18768763", ChannelId = "sms", IsBot = false };
message.Text = $"How are you doing? ";
message.Language = "en";
connector.Messages.SendMessage(message);
IBotData myDataBag = new JObjectBotData(message);
myDataBag.UserData.SetValue("Username", "Bob");
myDataBag.PerUserInConversationData.SetValue("Newuser", "yes");
Then in my main Dialog.cs I try to access it
public static readonly IDialog<string> dialog = Chain
.PostToChain()
.Switch(new Case<Message, IDialog<string>>((msg) =>
{
var regex = new Regex("hello$", RegexOptions.IgnoreCase);
return regex.IsMatch(msg.Text);
},
(ctx, msg) =>
{
// Clearing user related data upon logout
string isnewuser = ctx.PerUserInConversationData.TryGetValue("Newuser");
string username = ctx.UserData.TryGetValue("Username");
return Chain.Return($"Welcome {username}");
}))
.Unwrap()
.PostToUser();
I receive the message on my phone. However, I am not able to get back the username and newuser session data saved inside OAuthCallback.
I suspect that this is happening because the proactive message does not have conversationId set. And the conversationId must differ somehow.
so how can I get it to set session data to my proactive message in the future conversation?
In proactive's scenarios, the conversation Id for channels change when the user answers your message, it's like a new session, we do this type of features using the channel data, but this solution is only for small data, you also have the option of creating a persistent session using the same table storage that the bot framework is using to save the dialog context, in this solution you can create another table to store your data serialized, and the final one is a persistent session using a distributed cache like Redis, but this type of services are expensive, so you have to analyze which type of solution is the right one for your solution, but as a start, you should try with the Channel Data property and if it works, you can analyze another approach
I hope I have been helpful
Not sure if this is still relevant after four years, but I think I figured this out in Access UserProfile from NotifyBot. Check it out.