Skip to main content

Stripe

Zuora

Stripe

Zephr integrates out-of-the-box with the payment-processing company, Stripe, allowing you to set up payment journeys for users to purchase one-off or recurring subscriptions via Stripe, then receive the relevant product grants and user experience via Zephr.

In this guide, we’ll explore how to integrate with Stripe in Zephr Classic.

Integrating with Stripe

Navigate to Settings > Payment Providers within your Zephr Admin Console.

From here, scroll to the Stripe setting, then enter the configuration details based upon your pre-existing Stripe Account. You will need to provide the following details:

  • API Key
  • Public Key
  • Currency

Stripe has detailed documentation for finding your API keys, and checking your currency code.

Basic Stripe Configuration

Once your details are entered, click Save.

Linking Products to Stripe

Once you’ve completed your configuration, it’s time to create the relevant Products in Zephr. To do this, navigate to Entitlement Manager > Products, then click Add Product.

Name your Product accordingly, and assign an Entitlement Bundle, then, under Payment Methods, select Stripe (One-Off) or Stripe (Recurring).

One-Off Payments

Selecting the One-Off option lets you name your price point, and add a price, with the currency determined by your configuration settings mentioned above.

Repeat this with any other price points you wish to offer for your product.

One-Off Payments – Sample UI Component

 

Once you’ve created your product, you’ll want to link it to a payment form. In Zephr Classic, Payments are managed via UI Components.

To help you get started, we’ve listed the code for a sample paywall below.

Please note: this UI Component is meant for testing purposes only, and not intended to be used in a Production environment. For a branded, styled Paywall, you will need to create your own unique UI Component.

<script src="https://js.stripe.com/v3/"></script>

<form action="/charge" method="post" id="payment-form">
    <div class="form-row">
        <label for="card-element">
            Credit or debit card
        </label>
        <div id="card-element"></div>
        <div class="sr-field-error" id="card-errors" role="alert"></div>
    </div>
    <div class="sr-section hidden completed-view">
        <p id="result"></p>
    </div>
    <button id="submit">
        <div id="spinner" class="hidden"></div>
        <span id="button-text">Buy</span>
    </button>
</form>

<script>
    var stripe;

    var xhr = new (XMLHttpRequest || ActiveXObject)('MSXML2.XMLHTTP.3.0');
    xhr.open('GET', '/blaize/payment/stripe/publicKey', true);
    xhr.setRequestHeader('Content-type', 'application/json');
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
            var response;
            try {
                response = JSON.parse(xhr.response);
            } catch (e) {
                response = xhr.response;
            }
            stripe = Stripe(response)
            var elements = stripe.elements();
            var card = elements.create('card', {style: style});

            card.mount('#card-element');

            card.addEventListener('change', function (event) {
                var displayError = document.getElementById('card-errors');
                if (event.error) {
                    displayError.textContent = event.error.message;
                } else {
                    displayError.textContent = '';
                }
            });
            var form = document.getElementById('payment-form');
            form.addEventListener('submit', function (event) {
                event.preventDefault();
                document.querySelectorAll('.completed-view').forEach(function (view) {
                    view.classList.add('hidden');
                });
                stripe
                    .createPaymentMethod('card', card, {})
                    .then(function (result) {
                        if (result.error) {
                            showCardError(result.error);
                        } else {
                            buy(result.paymentMethod.id, card);
                        }
                    });
            });
        }
    };
    xhr.send();

    var productId = 'stripe-product';
    var pricePointId = "pp1";

    var style = {
        base: {
            color: '#32325d',
            fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
            fontSmoothing: 'antialiased',
            fontSize: '16px',
            '::placeholder': {
                color: '#aab7c4'
            }
        },
        invalid: {
            color: '#fa755a',
            iconColor: '#fa755a'
        }
    };


    async function buy(paymentMethod, card) {
        var xhr = new (XMLHttpRequest || ActiveXObject)('MSXML2.XMLHTTP.3.0');
        xhr.open('POST', '/blaize/payment/stripe/buy', true);
        xhr.setRequestHeader('Content-type', 'application/json');
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                var response;
                try {
                    response = JSON.parse(xhr.response);
                } catch (e) {
                    response = xhr.response;
                }
                handleAction(response, card)
            }
        };

        xhr.send(JSON.stringify({
            payment_method: paymentMethod,
            product_id: productId,
            price_point_id: pricePointId
        }));
    }

    function handleAction(backendResponse, card) {
        if(backendResponse.payment_error){
            document.querySelectorAll('.completed-view').forEach(function (view) {
                view.classList.remove('hidden');
            });
            document.getElementById("result").textContent = backendResponse.payment_error
        } else if (backendResponse.grant_id) {
            document.querySelectorAll('.completed-view').forEach(function (view) {
                view.classList.remove('hidden');
            });
            document.getElementById("result").textContent = "Payment successful! Grant id: " + backendResponse.grant_id;
        } else {
            stripe.confirmCardPayment(backendResponse.client_secret, {
                payment_method: {
                    card: card
                }
            }).then(function (result) {
                if (result.error) {
                    document.querySelectorAll('.completed-view').forEach(function (view) {
                        view.classList.remove('hidden');
                    });
                    document.getElementById("result").textContent = result.error.message;
                } else {
                    var xhr = new (XMLHttpRequest || ActiveXObject)('MSXML2.XMLHTTP.3.0');
                    xhr.open('POST', '/blaize/payment/stripe/buy', true);
                    xhr.setRequestHeader('Content-type', 'application/json');
                    xhr.onreadystatechange = function () {
                        if (xhr.readyState === 4) {
                            var response;
                            try {
                                response = JSON.parse(xhr.response);
                            } catch (e) {
                                response = xhr.response;
                            }
                            if (response.grant_id) {
                                document.querySelectorAll('.completed-view').forEach(function (view) {
                                    view.classList.remove('hidden');
                                });
                                document.getElementById("result").textContent = "Payment successful! " + JSON.stringify(response);
                            } else {
                                document.querySelectorAll('.completed-view').forEach(function (view) {
                                    view.classList.remove('hidden');
                                });
                                document.getElementById("result").textContent = "Card was not properly authenticated";

                            }
                        }
                    };

                    xhr.send(JSON.stringify({
                        product_id: productId,
                        price_point_id: pricePointId,
                        payment_intent_id: backendResponse.payment_intent_id
                    }));
                }
            });
        }
    }
</script>

Please note, in the above example, the following lines have been hardcoded and will need to be updated.

var productId = 'stripe-product';
var pricePointId = "pp1";

var productId = 'stripe-product'; references the slug given to the Product in the product creation example above. Update this accordingly, based on your Product Slug, which can be found on your Product’s info page, under the Label.

var pricePointId = "pp1"; references the price point label you have set. Update this inline with the price point slug, and add additional lines for each additional price point.

Once created, your UI Component can be used within any Feature Rule to test payment.

Recurring Payments

Selecting the Recurring option allows you to set a Stripe Plan ID, linking the Zephr Product to a Stripe Product and Plan.

 

Stripe Recurring Payment with Plan ID

 

You will find the Plan ID in your Stripe Dashboard, by navigating to Products, then clicking into the relevant product. The Plan ID can be found in the URL like so:products/{plan_id} or in the Details section like so:

Stripe - Plan ID

Once complete, click Add Product.

Recurring Payments – Sample UI Component

Once you’ve created your product, you’ll want to link this to a payment form. In Zephr Classic, Payments are managed via UI Components.

To help you get started, we’ve listed the code for a sample paywall below.

Please note: this UI Component is meant for testing purposes only, and not intended to be used in a Production environment.

Navigate to UI > UI Components. Click Add UI Component, then paste the following:

<script src="https://js.stripe.com/v3/"></script>

<form action="/charge" method="post" id="payment-form">
    <div class="form-row">
        <label for="card-element">
            Credit or debit card
        </label>
        <div id="card-element"></div>
        <div class="sr-field-error" id="card-errors" role="alert"></div>
    </div>
    <div class="sr-section hidden completed-view">
        <p id="result"></p>
    </div>
    <button id="submit">
        <div id="spinner" class="hidden"></div>
        <span id="button-text">Subscribe</span>
    </button>
</form>

<script>
    var stripe;

    var xhr = new (XMLHttpRequest || ActiveXObject)('MSXML2.XMLHTTP.3.0');
    xhr.open('GET', '/blaize/payment/stripe/publicKey', true);
    xhr.setRequestHeader('Content-type', 'application/json');
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
            var response;
            try {
                response = JSON.parse(xhr.response);
            } catch (e) {
                response = xhr.response;
            }
            stripe = Stripe(response)
            var elements = stripe.elements();
            var card = elements.create('card', {style: style});

            card.mount('#card-element');

            card.addEventListener('change', function (event) {
                var displayError = document.getElementById('card-errors');
                if (event.error) {
                    displayError.textContent = event.error.message;
                } else {
                    displayError.textContent = '';
                }
            });
            var form = document.getElementById('payment-form');
            form.addEventListener('submit', function (event) {
                event.preventDefault();
                document.querySelectorAll('.completed-view').forEach(function (view) {
                    view.classList.add('hidden');
                });
                stripe
                    .createPaymentMethod('card', card, {})
                    .then(function (result) {
                        if (result.error) {
                            showCardError(result.error);
                        } else {
                            createCustomer(result.paymentMethod.id);
                        }
                    });
            });
        }
    };
    xhr.send();

    var productId = 'stripe-product';

    var style = {
        base: {
            color: '#32325d',
            fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
            fontSmoothing: 'antialiased',
            fontSize: '16px',
            '::placeholder': {
                color: '#aab7c4'
            }
        },
        invalid: {
            color: '#fa755a',
            iconColor: '#fa755a'
        }
    };

    async function createCustomer(paymentMethod) {
        var xhr = new (XMLHttpRequest || ActiveXObject)('MSXML2.XMLHTTP.3.0');
        xhr.open('POST', '/blaize/payment/stripe/subscribe', true);
        xhr.setRequestHeader('Content-type', 'application/json');
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                var response;
                try {
                    response = JSON.parse(xhr.response);
                } catch (e) {
                    response = xhr.response;
                }
                handleAction(response)
            }
        };

        xhr.send(JSON.stringify({
            payment_method: paymentMethod,
            product_id: productId
        }));
    }


    function showCardError(error) {
        changeLoadingState(false);
        // card declined
        var errorMsg = document.querySelector('.sr-field-error');
        errorMsg.textContent = error.message;
        setTimeout(function () {
            errorMsg.textContent = '';
        }, 8000);
    }

    var changeLoadingState = function (isLoading) {
        if (isLoading) {
            document.querySelector('#spinner').classList.add('loading');
            document.querySelector('button').disabled = true;

            document.querySelector('#button-text').classList.add('hidden');
        } else {
            document.querySelector('button').disabled = false;
            document.querySelector('#spinner').classList.remove('loading');
            document.querySelector('#button-text').classList.remove('hidden');
        }
    };

    function handleAction(backendResponse) {
        if (backendResponse.paymentIntentStatus === "requires_action") {
            stripe.confirmCardPayment(backendResponse.clientSecret).then(function (result) {
                var xhr = new (XMLHttpRequest || ActiveXObject)('MSXML2.XMLHTTP.3.0');
                if (result.error) {
                    changeLoadingState(false);
                    showCardError(result.error);
                } else {
                    xhr.open('POST', '/blaize/payment/stripe/subscription/confirmation', true);
                    xhr.setRequestHeader('Content-type', 'application/json');
                    xhr.onreadystatechange = function () {
                        if (xhr.readyState === 4) {
                            var response;
                            try {
                                response = JSON.parse(xhr.response);
                            } catch (e) {
                                response = xhr.response;
                            }
                        }
                    };
                    xhr.send(JSON.stringify({
                        subscriptionId: backendResponse.subscriptionId
                    }))

                }
            })
        } else if (backendResponse.paymentIntentStatus === "requires_payment_method") {
            document.querySelectorAll('.completed-view').forEach(function (view) {
                view.classList.remove('hidden');
            });
            document.getElementById("result").textContent = "Payment has failed - please enter a new payment method to setup subscription"
        } else if (backendResponse.grant_id) {
            orderComplete(backendResponse)
        } else {
            document.querySelectorAll('.completed-view').forEach(function (view) {
                view.classList.remove('hidden');
            });
            document.getElementById("result").textContent = "Something went wrong with your payment";
        }
    }

    var orderComplete = function (backendResponse) {
        console.log('grant_id: ' + backendResponse.grant_id)
        document.querySelectorAll('.payment-view').forEach(function (view) {
            view.classList.add('hidden');
        });
        document.querySelectorAll('.completed-view').forEach(function (view) {
            view.classList.remove('hidden');
        });
        document.getElementById("result").textContent = "Payment successful. Grant id: " + backendResponse.grant_id;
    };
</script>

Please note, in the above example the line var productId = 'stripe-product'; references the slug given to the Product in the product creation example above. When using this UI Component, update this accordingly, based on your Product Slug, which can be found on your Product’s info page, under the Label.

Once created, your UI Component can be used within any Feature Rule to test your payment configuration.

Testing your Configuration

Once your payment form has been created, and a rule to display it has been published, navigate to Preview Mode and follow the user journey required to see the paywall.

A series of test card numbers are available in the Stripe Documentation to allow you to test payment. Following successful payment, the UI Component will return the grant id, and your user will be granted the relevant product within Zephr.