Skip to content

base33/Cashier

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Umbraco Cashier

Cashier is an agnostic payment gateway solution for taking Card Payments and setting up Direct Debits in sites built with Umbraco. The aim of this project is to be able to write payment solutions once that can be reused in any project, and provide a constistent code pattern for taking payments, no matter what Payment Provider is being used.

During my time as a web developer, I have found most people don't want full fledged e-commerce systems. More often than not, people want to be able to take donations, or pay for something that is pushed into an already-existing system they use to manage orders. As a result, each implementation is unique but the payment integration stays the same. Hence this package.

Cashier also provides a very handy Dashboard to view all Payments requested by code, so it's possible to view transaction successes or failures.

Contents:

Installation and example of taking Card Payment

Step 1

Install via Nuget:

install-packge Our.Umbraco.Cashier

Step 2

Payment Intents are stored in a database. This can point to your Umbraco database. A table called PaymentIntents will be created.

<add name="CashierDb" connectionString="{{CONNECTION STRING HERE}}" providerName="System.Data.SqlServerCe.4.0" />

Step 3

Install a Cashier Payment Provider solution package: (currently only stripe exists)

install-package Our.Umbraco.Cashier.Stripe

For instructions on how to finish the Stripe setup (configure keys etc.), click here.

Step 4

Create an Umbraco Composition to register your selected payment gateway. You only need to register what your are planning to use.

public class RegisterPaymentGatewayComposer : IUserComposer
{
    public void Compose(Composition composition)
    {
        composition.RegisterCardPaymentGateway<StripeCardPaymentGateway>();
        composition.RegisterDirectDebitPaymentGateway<StripeDirectDebitPaymentGateway>();
    }
}

Step 5

Now you are ready to take payment. First of all, set up a controller that injects ICashier

public class DonationFormSurfaceController : SurfaceController
{
    public ICashier Cashier { get; }

    public DonationFormSurfaceController(ICashier cashier)
    {
        Cashier = cashier;
    }
        
    public ActionResult HandleDonation(DonationFormModel model)
    {
        //<---- we will take payment here
    }
}

Now we have a controller, all we need to do is arrange for payment to be taken. To do this, all we do is:

var paymentIntentRequest = PaymentIntentRequest.CreateCardPayment(
                                transactionReference: transactionReference,
                                description: "Donation",
                                amount: model.Amount,
                                customerEmail: model.Email,
                                confirmationPageUrl: CurrentPage.Url,
                                failurePageUrl: Umbraco.Content(1148).Url,
                                callbackUrl: "/umbraco/surface/DonationFormSurface/HandleCallback",
                                additionalData: ""
                          );

//here I am going to store the donation model against the PaymentIntent so that I can load it later to complete the process
paymentIntentRequest.AdditionalData = JsonConvert.SerializeObject(model);

var paymentIntentCreated = Cashier.CreateNewPaymentIntent(paymentIntentRequest);

return paymentIntentCreated.ActionResult;

At this point, we have now returned the ActionResult returned from the Payment Provider implementation. We will need to write the callback method so that Cashier can tell us whether the payment was successful or not. This callback is asynchronous and will happen in the background.

You will see that we have already set the callbackUrl to /umbraco/surface/DonationFormSurface/HandleCallback. Let's write it.

The success page, failure page and callback url will recieve two querystring parameters tr and hs. The tr parameter is the Transaction Reference and the hs parameter is handshake.

We will need to get the PaymentIntent from Cashier using the Transaction Reference and validate the Handshake is correct. The Handshake is secret and only known by Cashier.

public ActionResult HandleCallback(string tr, string hs)
{
    var paymentIntent = Cashier.GetPaymentIntentByTransactionRef(tr);
            
    //validate the handshake matches
    if(paymentIntent.HandShake != hs)
    {
        return new HttpStatusCodeResult(400);
    }
    
    //now we can load our donation model out
    var model = JsonConvert.DeserializeObject<DonationFormModel>(paymentIntent.AdditionalData);
    
    if(paymentIntent.PaymentStatus == PaymentStatus.Succeeded)
    {
        //do whatever here: register in CRM, send email, etc.
    }
}

Remember! The success page should validate the PaymentIntent handshake in the exact same way.

How to set up Direct Debits

For Direct Debits, the code and process is exactly the same, except you create the PaymentIntentRequest like this:

var paymentIntentCreated = Cashier.CreateNewPaymentIntent(PaymentIntentRequest.CreateDirectDebit(
                                transactionReference: transactionReference,
                                description: "Donation Direct Debit",
                                amount: model.Amount,
                                customerEmail: model.Email,
                                confirmationPageUrl: CurrentPage.Url,
                                failurePageUrl: Umbraco.Content(1148).Url,
                                callbackUrl: "/umbraco/surface/DonationFormSurface/HandleCallback",
                                additionalData: jsonDonation,
                                customerUniqueReference: model.Email,
                                directDebitStartDate: FirstDayOfNextMonth(DateTime.UtcNow), //start date
                                customerAddress: new CustomerAddress
                                {
                                    AddressLines = model.AddressLines,
                                    City = model.Town,
                                    Country = "United Kingdom",
                                    Postcode = model.Postcode
                                }
                          ));

Dashboard UI

The Dashboard allows users to view all Payment Requests made by code and see the current status of those.

Alt text

Conceptual flow diagram

Alt text

Contributing

I really hope the community will contribute to make this package better for everyone who hates spending so much time writing payment providers over and over again!

Please feel free to contribute in the following ways:

  • Extend the Cashier core system to support more types of Payment Providers
  • Implement and submit Pull Requests for new Payment Providers

I would really like to see implementations for the following:

  • SagePay
  • Worldpay
  • PayPal
  • BitPay
  • ... basically as many as we can Implement!

In summary, I would like this to be a community owned project to simplify the codebases we support every day and to allow for more time making the CMS experience better.

Running this project locally

Run the Cashier.TestBed project.

Username: [email protected] Password: qwe1234567

Available Cashier Payment Providers

  • Stripe

Stripe

Install the package:

install-package Our.Umbraco.Cashier.Stripe

Register the card and direct debit payment gateways with Cashier

public class RegisterPaymentGatewayComposer : IUserComposer
{
    public void Compose(Composition composition)
    {
        composition.Register(typeof(ICardPaymentGateway), typeof(StripeCardPaymentGateway), Lifetime.Request);
        composition.Register(typeof(IDirectDebitPaymentGateway), typeof(StripeDirectDebitPaymentGateway), Lifetime.Request);
    }
}

Stripe requires a public key and secret key. Get these from the Stripe dashboard. The Stripe Payment provider sets up a virtual routed Card and Direct Debit url that can be changed in the app settings.

Configure the AppSettings:

<add key="Cashier:Stripe:Public" value="{{INSERT STRIPE PUBLIC KEY HERE}}" />
<add key="Cashier:Stripe:Secret" value="{{INSERT STRIPE SECRET KEY HERE}}" />
<add key="Cashier:Stripe:CardPaymentUrl" value="pay-by-card" />
<add key="Cashier:Stripe:CardPaymentPageVirtualNode" value="{{INSERT Payment Page virtual node ID}}" />
<add key="Cashier:Stripe:DirectDebitPaymentUrl" value="set-up-direct-debit" xdt:Transform="InsertIfMissing" />
<add key="Cashier:Stripe:DirectDebitPaymentPageVirtualNode" value="{{INSERT Payment Page virtual node ID}}" />
<add key="Cashier:Stripe:LiveMode" value="false" />

You can customise the views installed in the Views directory.