Creating a Stripe Payments Plugin for Sitecore Commerce Engine
Nov 22, 2017
In this post, I will cover the general concepts of developing plugins for Sitecore Commerce Engine. To illustrate the process, I developed a plugin that integrates with Stripe as the payment processing system. Here are the steps for the development of a plugin:
- Create a VS .netcore project (in this example it is named
Oshyn.Plugin.SC.Payments.Stripe
). - Since we are replacing the payment processing in Sitecore Commerce Engine, remove the reference to BrainTreePayments Plugin project and add a reference to this newly Created project in the Sitecore commerce engine project. Because we are making Stripe our payment processor, we are removing references to the BrainTreePayment plugin.
- Create a Stripe account using this page. Then find the
PublishableKey
,SecretKey
from the dashboard. - In the
wwwroot/data/Environment/Plugin.Habitat.CommerceAuthoring-1.0.0.json
file remove theBrainTreeClientPolicy
StripeClientPolicy{ "$type":"Oshyn.Plugin.SC.Payments.Stripe.StripeClientPolicy, Oshyn.Plugin.SC.Payments.Stripe", "Environment":"sandbox", "PublishableKey":"pk_test_abc133424242424242", //publishable key provided by Stripe "SecretKey":"sk_test_abc133424424424424442", //secret key provided by Stripe "ConnectTimeout": 120000, "PolicyId":"d30f5f4bb31041ca99006640f0309e51", //just generate random Guid and use it here "Models": { "$type":"System.Collections.Generic.List`1[[Sitecore.Commerce.Core.Model, Sitecore.Commerce.Core]], mscorlib", "$values": [] }
- Create the object that reads this configuration as
StripeClientPolicy
as shown below:StripeClientPolicynamespace Oshyn.Plugin.SC.Payments.Stripe { using Sitecore.Commerce.Core; public class StripeClientPolicy : Policy { /// <summary> /// Initializes a new instance of the <see cref="StripeClientPolicy" /> class. /// </summary> public StripeClientPolicy() { this.Environment = string.Empty; this.PublishableKey = string.Empty; this.SecretKey = string.Empty; } /// <summary> /// Gets or sets the environment. /// </summary> /// <value> /// The environment. /// </value> public string Environment { get; set; } /// <summary> /// Gets or sets the publishable key. /// </summary> /// <value> /// The publishable key. /// </value> public string PublishableKey { get; set; } /// <summary> /// Gets or sets the Secret key. /// </summary> /// <value> /// The Secret key. /// </value> public string SecretKey { get; set; } } }
- Create
CreateFederatedPaymentBlock
which actually does the integration to Stripe:CreateFederatedPaymentBlocknamespace Oshyn.Plugin.SC.Payments.Stripe { using Sitecore.Framework.Pipelines; using System.Threading.Tasks; using Sitecore.Commerce.Core; using Sitecore.Framework.Conditions; using Sitecore.Commerce.Plugin.Orders; using Sitecore.Commerce.Plugin.Payments; using global::Stripe; using System; public class CreateFederatedPaymentBlock : PipelineBlock<CartEmailArgument, CartEmailArgument, CommercePipelineExecutionContext> { public override Task<CartEmailArgument> Run(CartEmailArgument arg, CommercePipelineExecutionContext context) { Condition.Requires(arg).IsNotNull($"{this.Name}: The cart can not be null"); var cart = arg.Cart; if (!cart.HasComponent<FederatedPaymentComponent>()) { return Task.FromResult(arg); } //get the payment component var payment = cart.GetComponent<FederatedPaymentComponent>(); if (string.IsNullOrEmpty(payment.PaymentMethodNonce)) { context.Abort(context.CommerceContext.AddMessage( context.GetPolicy<KnownResultCodes>().Error, "InvalidOrMissingPropertyValue", new object[] { "PaymentMethodNonce" }, $"Invalid or missing value for property 'PaymentMethodNonce'."), context); return Task.FromResult(arg); } //get the stripeclient policy var stripeClientPolicy = context.GetPolicy<StripeClientPolicy>(); if (string.IsNullOrEmpty(stripeClientPolicy?.Environment) || string.IsNullOrEmpty(stripeClientPolicy?.PublishableKey) || string.IsNullOrEmpty(stripeClientPolicy?.SecretKey)) { context.CommerceContext.AddMessage( context.GetPolicy<KnownResultCodes>().Error, "InvalidClientPolicy", new object[] { "StripeClientPolicy" }, $"{this.Name}. Invalid StripeClientPolicy"); return Task.FromResult(arg); } StripeConfiguration.SetApiKey(stripeClientPolicy.SecretKey); var customers = new StripeCustomerService(); var charges = new StripeChargeService(); // this page https://stripe.com/docs/testing provides for // testing cards and testing tokens for example: tok_visa StripeCustomer customer = null; //Currently we are not storing customerId created, but you could store the customer.Id //in your user database as stripeCustomerId and can be used later using the customers.Get //to do recurrent charges. customer = customers.Create(new StripeCustomerCreateOptions { Email = arg.Email, SourceToken = payment.PaymentMethodNonce }); StripeCharge charge = charges.Create(new StripeChargeCreateOptions { Amount = Convert.ToInt32(payment.Amount.Amount*100), Description = "Sample Payment Description", Currency = payment.Amount.CurrencyCode, CustomerId = customer.Id }); //if the charge was successful if (charge != null && string.IsNullOrEmpty(charge.FailureCode) && string.IsNullOrEmpty(charge.FailureMessage)) { payment.TransactionId = charge.Id; payment.TransactionStatus = charge.Status; //use cardid or card fingerprint or last4 based on business needs payment.MaskedNumber = charge.Source.Card.Fingerprint; payment.ExpiresMonth = charge.Source.Card.ExpirationMonth; payment.ExpiresYear = charge.Source.Card.ExpirationYear; } else { context.Abort(context.CommerceContext.AddMessage( context.GetPolicy<KnownResultCodes>().Error, "CreatePaymentFailed", new object[] { "PaymentMethodNonce" }, $"{this.Name}. Create payment failed for Stripe:{charge.FailureMessage}"), context); } return Task.FromResult(arg); } } }
- Register the
CreateFederatedPaymentBlock
. This assumes that the BrainTreePayment reference is removed from Commerce Engine, as you do not want to process payments with the BrainTreePayment integration. You can look at the latest log file inCommerceAuthoring\wwwroot\logs\NodeConfiguration_Deployment01_*.txt
. This provides the list of pipelines and the blocks registered for each pipeline. You can append or override existing block registrations with your new registration as follows:CreateFederatedPaymentBlocknamespace Oshyn.Plugin.SC.Payments.Stripe { using Microsoft.Extensions.DependencyInjection; using Sitecore.Commerce.Core; using Sitecore.Framework.Configuration; using System.Reflection; using Sitecore.Framework.Pipelines.Definitions.Extensions; using Sitecore.Commerce.Plugin.Orders; using Sitecore.Commerce.Plugin.Payments; /// <summary> /// The carts configure sitecore class. /// </summary> public class ConfigureSitecore : IConfigureSitecore { /// <summary> /// The configure services. /// </summary> /// <param name="services"> /// The services. /// </param> public void ConfigureServices(IServiceCollection services) { var assembly = Assembly.GetExecutingAssembly(); services.RegisterAllPipelineBlocks(assembly); //modifying the ICreateOrderPipeline and adding our Block //before the CreateOrderBlock services.Sitecore().Pipelines(config => config.ConfigurePipeline<ICreateOrderPipeline>(d => { d.Add<Oshyn.Plugin.SC.Payments.Stripe.CreateFederatedPaymentBlock>().Before<CreateOrderBlock>(); })); } } }
- Debug it in place by running the Sitecore.Commerce.Engine and first running
http://{server:port}/CommerceOps/Bootstrap()
, which will load the updatedStripeClientPolicy
sections into the database. - Import the Postman folder in
Sitecore.Commerce.SDK.1.0.2301
into the Postman app. Assume your Visual Studio project is running at some address such aslocalhost:5005
, you need to go to your postman collection and update the variables so that theServiceHost
,OpsApiHost
,AuthoringHost
are set tolocalhost:5005
instead of the defaultlocalhost:5000
. Then you can use the postman API collection provided as follows:- execute the
CartAPISamples\Get Car Cart01
API - this creates an empty cart named "Cart 01" - execute the
CartAPISamples\Add Cart Line with Variant
API - this adds a line item to the "Cart 01" - execute the
CartAPISamples\Set Cart to Physical Fullfillment
- this fills the fulfillment component with an address - execute the
CartAPISamples\Add Federated Payment
- in this request updatePaymentMethodNonce
: “tok_visa” as follows and then execute. This will add payment component data. - Now go to and execute
OrdersAPISamples\Create order
- this will invoke theICreateOrderPipeline
and inherently the block we registered and creates a payment via Stripe.
- execute the
- Now you can go to your Stripe Dashboard and see that it records a payment corresponding to the above transaction.
By following a similar approach, you can extend the relevant pipelines to process refunds using the Stripe service.
I hope this helps in not only showing how to integrate with the Stripe payment service, but by also illustrating the approach that can be used to write other plugins to extend the Sitecore.Commerce.Engine
platform.
Related Insights
-
Esteban Bustamante
-
Oshyn
Sitecore Symposium 2024
Recap & Looking Ahead to the Future
-
-
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.