NAME

Bod::Stripe - Simple way to implement payments using Stripe hosted checkout

SYNOPSIS

  use Bod::Stripe;
  
  my $stripe = Bod::Stripe->new(
          'api-secret'  => 'sk_test_00000000000000000000000000',
  );
  
  # Note price is in lowest currency unit (i.e pence or cents not pounds or dollars)
  $stripe->add_product(
          'id'          => 1,
          'name'        => 'My product',
          'qty'         => 4,
          'price'       => 250,
  );
  
  foreach my $id($stripe->list_products) {
      print "$id is " . $stripe->get_product($id)->{'name'} . "\n";
  }
  
  $stripe->checkout(
          'api-public'  => 'pk_test_00000000000000000000000000',
  );
  

DESCRIPTION

A simple to use interface to the Stripe payment gateway utilising the Stripe hosted checkout. The only dependencies are the core modules HTTP::Tiny and JSON::PP.

Bod::Stripe has a Trolley into which products are loaded. Once the Trolley is full of the product(s) in the order, this is passed to the Stripe hosted checkout either using Javascript provided by Stripe (see https://stripe.com/docs/payments/accept-a-payment?integration=checkout), Javascript provided in this document or the checkout utility method that allows a server side script to send the user to Stripe invisibly.

At present Bod::Stripe only handles simple, one-off payments. Manipulation of customers, handling subscriptions and user hosted checkout is not supported. However, this implementation makes payment for a single item or group of items simple to implement.

Keys

Stripe provides four API Keys. A Secret and a Publishable (called Public within Bod::Stripe) for both testing and for live transactions. When calling the new method it is necessary to provide the Secret Key. Before calling the checkout method to redirect the user to the Stripe hosted checkout, the Public Key is also required so this is usually provided to the new method.

See https://stripe.com/docs/keys

Workflow

The basic workflow for Bod::Stripe is to initially create an instance of the module with at minimum the Secret Key. If using a currency other than GBP this should also be set at this time.

  my $stripe = Bod::Stripe->new(
          'api-public' => 'pk_test_00000000000000000000000000',
          'api-secret' => 'sk_test_00000000000000000000000000',
          'currency'   => 'USD',
  );

Next, products are assembled in the Trolley. There are methods to add, update, remove and list the products in the Trolley.

  $stripe->add_product(
          'id'          => 1,
          'name'        => 'My product',
          'qty'         => 4,
          'price'       => 250,
  );
  my @products = $stripe->list_products;

Once the Trolley contains all the products, the user is redirected to the Stripe hosted checkout where they pay for the Trolley. Once this happens, Stripe returns to your site using one of the URLs provided delending on whether the payment was successful or not. Where no return URLs are provide, the script URL is used although in practice this is not usually sufficient and return URLs will be needed.

  $stripe->checkout;
  

Examples of other ways of redirecting the user to the Stripe hosted checkout and listed in the Examples section.

METHODS

new

  Bod::Stripe->new('api-secret' => 'sk_test_00000000000000000000000000');
  

The constructor method. The Secret Key is required.

The following parameters may be provided:

success

Returns true is the last method call was successful

error

Returns the last error message or an empty string if success returned true

Trolley Methods

add_product

Adds a product to the Trolley. Or update the product if an existing id is provided.

A product consists of the following hash entries

On success, returns the number of products in the Trolley

delete_product(id)

Delete the product with the specified id

On success, returns the number of products in the Trolley

list_products

Returns an array contining the IDs of the products in the Trolley

get_product(id)

On success, returns a hash with the product details. Each key of the hash corresponds to items listed for add_product

Checkout Methods

parameters

The following methods all take the following optional parameters. See new for their descriptions.

get_intent

This method will not normally need calling.

Returns the full session intent from Stripe if successful or the Stripe error otherwise.

get_intent_id

Returns the intend_id that needs passing to the Stripe hosted checkout if successful or the Stripe error otherwise.

get_id

In addition to the parameters listed above, this method also accepts the following optional parameters

Provides the Public Key and Intent Session ID as these are the two pieces of information required by the Javascript provided by Stripe and te Javacsript provided here. If text output is used (the default) the Public Key and Intent Session ID are provided as a colon separated string.

checkout

A simple implementation of redirecting the user to the Stripe hosted checkout.

Calling this method provides a fully formed HTML document including the Content-Type header that can be sent to the users browser. The HTML document contains all the Javascript required to sent the user to the Stripe hosted checkout transparently. Unless you are building a checkout with entirely AJAX calls, you will almost certainly want to use this method.

EXAMPLES

1 - Using the Stripe provided Javascript

See https://stripe.com/docs/payments/accept-a-payment?integration=checkout

Javascript

  <html>
    <head>
      <title>Buy cool new product</title>
      <script src="https://js.stripe.com/v3/"></script>
    </head>
    <body>
      <button id="checkout-button">Checkout</button>
  
      <script type="text/javascript">
        // Create an instance of the Stripe object with your publishable API key
        var stripe = Stripe('pk_test_00000000000000000000000000');
        var checkoutButton = document.getElementById('checkout-button');
  
        checkoutButton.addEventListener('click', function() {
          // Create a new Checkout Session using the server-side endpoint you
          // created in step 3.
          fetch('https://example.com/cgi-bin/trolley.pl?stripe=getIntent', {
            method: 'POST',
          })
          .then(function(response) {
            return response.json();
          })
          .then(function(session) {
            return stripe.redirectToCheckout({ sessionId: session.id });
          })
          .then(function(result) {
            // If `redirectToCheckout` fails due to a browser or network
            // error, you should display the localized error message to your
            // customer using `error.message`.
            if (result.error) {
              alert(result.error.message);
            }
          })
          .catch(function(error) {
            console.error('Error:', error);
          });
        });
      </script>
    </body>
  </html>

Perl trolley.pl

  use Bod::Stripe;
  use strict;
  use CGI;
  
  my $cgi = CGI->new;
  
  if ($cgi->param('stripe') eq 'getIntent') {
          my $stripe = Bod::Stripe->new(
            'api-public'    => 'pk_test_00000000000000000000000000',
            'api-secret'    => 'sk_test_00000000000000000000000000',
            'success-url'   => 'https://www.example.com/yippee.html',
            'cancel-url'    => 'https://www.example.com/ohdear.html',
            'reference'     => 'My Payment',
      );
      
      $stripe->add_product(
                  'id'          => 'test',
                  'name'        => 'Expensive Thingy',
                  'description' => 'Special edition version',
                  'qty'         => 1,
                  'price'       => 50000,
      );
      
      print "Content-Type: text/json\n\n";
      print $stripe->get_intent;
  }
  

2 - Simpler Javascript using XHR

Javascript

  <html>
  <head>
  <script src="https://js.stripe.com/v3/"></script>
  <script>
  var xhr=new XMLHttpRequest();
  function checkout() {
      xhr.open("POST", "https://www.example.com/cgi-bin/trolley.pl", true);
      xhr.onreadystatechange=function() {
      if (xhr.readyState == 4 && xhr.status == 200) {
                  var keys = xhr.response.split(':');
                  var stripe = Stripe(keys[0]);
                  var result = stripe.redirectToCheckout({ sessionId: keys[1] });
                  if (result.error) {
                          alert(result.error.message);
                  }
          }
          xhr.send("stripe=getKeys");
  }
  </head>
  <body>
  <input type="button" value="Buy Now!" onClick="checkout();">
  </body>
  </html>
  

Perl - trolley.pl

  use Bod::Stripe;
  use strict;
  use CGI;
  
  my $cgi = CGI->new;
  
  if ($cgi->param('stripe') eq 'getKeys') {
          my $stripe = Bod::Stripe->new(
            'api-public'    => 'pk_test_00000000000000000000000000',
            'api-secret'    => 'sk_test_00000000000000000000000000',
            'success-url'   => 'https://www.example.com/yippee.html',
            'cancel-url'    => 'https://www.example.com/ohdear.html',
            'reference'     => 'My Payment',
      );
      
      $stripe->add_product(
                  'id'          => 'test',
                  'name'        => 'Expensive Thingy',
                  'description' => 'Special edition version',
                  'qty'         => 1,
                  'price'       => 50000,
      );
      
      print "Content-Type: text/text\n\n";
      print $stripe->get_ids;
  }
  

3 - Simpest method (no Javascript required)

HTML

  <html>
  <head>
  <title>Simple Checkout</title>
  </head>
  <body>
  <form method="post" action="https://www.example.com/cgi-bin/trolley.pl">
  <input type="hidden" name="stripe" action="checkout">
  <label for="qty">How many do you want?</label>
  <input type="number" name="qty" min="1" name="qty">
  <input type="submit" value="Buy Now!">
  </form>
  </body>
  </html>
  

Perl - trolley.pl

  use Bod::Stripe;
  use strict;
  use CGI;
  
  my $cgi = CGI->new;
  
  if ($cgi->param('stripe') eq 'checkout') {
          my $stripe = Bod::Stripe->new(
            'api-public'    => 'pk_test_00000000000000000000000000',
            'api-secret'    => 'sk_test_00000000000000000000000000',
            'success-url'   => 'https://www.example.com/yippee.html',
            'cancel-url'    => 'https://www.example.com/ohdear.html',
            'reference'     => 'My Payment',
      );
      
      $stripe->add_product(
                  'id'          => 'test',
                  'name'        => 'Expensive Thingy',
                  'description' => 'Special edition version',
                  'qty'         => $cgi->param('qty'),
                  'price'       => 50000,
      );
      
      if ($stripe->success) {
              print $stripe->checkout;
          } else {
                  # handle errors...
          }
  }

This last example prints out a fully formed HTML to the browser complete with Content-Type header. If other headers are required, such as Set-Cookie headers, they can be included immediately before printing calling checkout.

SEE ALSO

Net::Stripe, Net::Stripe::Simple, Business::Stripe

AUTHOR

COPYRIGHT AND LICENSE

This software is copyright (c) 2021 by Ian Boddison.

All rights reserved.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.