/stripe-guides

How to integrate Stripe without a website?

Learn how to integrate Stripe payments without a website for mobile apps, IoT, or backend systems. Step-by-step guide for secure, code-based payment processing.

Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Book a free consultation

How to integrate Stripe without a website?

Integrating Stripe Without a Website: A Comprehensive Tutorial

 

Introduction

 

Integrating Stripe without a traditional website is entirely possible and can be valuable for various use cases such as mobile apps, IoT devices, CLI applications, or backend systems that need payment processing capabilities. This tutorial will guide you through the complete process of integrating Stripe's payment processing capabilities without needing a conventional website.

 

Step 1: Set Up a Stripe Account

 

Before you begin integration, you need to create and set up a Stripe account:

  1. Go to Stripe's website (https://stripe.com) and sign up for an account
  2. Complete the verification process and business details
  3. Navigate to the Dashboard to find your API keys
  4. Make note of both your publishable key and secret key

 

Step 2: Install the Stripe SDK/Library

 

Depending on your platform, install the appropriate Stripe library:

For Node.js:


npm install stripe

For Python:


pip install stripe

For PHP:


composer require stripe/stripe-php

For Ruby:


gem install stripe

For Java:


// Add to your pom.xml

  com.stripe
  stripe-java
  20.136.0

 

Step 3: Initialize the Stripe Client

 

After installing the library, initialize it with your secret key:

Node.js:


const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');

Python:


import stripe
stripe.api_key = "sk_test_YOUR_SECRET\_KEY"

PHP:


require\_once 'vendor/autoload.php';
\Stripe\Stripe::setApiKey('sk_test_YOUR_SECRET_KEY');

Ruby:


require 'stripe'
Stripe.api_key = 'sk_test_YOUR_SECRET\_KEY'

Java:


import com.stripe.Stripe;
import com.stripe.model.\*;
import com.stripe.param.\*;

Stripe.apiKey = "sk_test_YOUR_SECRET_KEY";

 

Step 4: Create a Payment Intent

 

A PaymentIntent is Stripe's way of representing your intent to collect payment from a customer:

Node.js:


async function createPaymentIntent() {
  try {
    const paymentIntent = await stripe.paymentIntents.create({
      amount: 1000, // Amount in cents
      currency: 'usd',
      payment_method_types: ['card'],
      description: 'Payment for order #12345',
      metadata: {
        order\_id: '12345',
        customer\_name: 'John Doe'
      }
    });
    
    return paymentIntent;
  } catch (error) {
    console.error('Error creating payment intent:', error);
    throw error;
  }
}

Python:


def create_payment_intent():
    try:
        payment\_intent = stripe.PaymentIntent.create(
            amount=1000,  # Amount in cents
            currency="usd",
            payment_method_types=["card"],
            description="Payment for order #12345",
            metadata={
                "order\_id": "12345",
                "customer\_name": "John Doe"
            }
        )
        
        return payment\_intent
    except Exception as e:
        print(f"Error creating payment intent: {e}")
        raise

 

Step 5: Collect Payment Details Through Your Non-Web Application

 

Depending on your application type, you'll need to collect payment details. Here's how to do this for different applications:

For a Mobile App (using Stripe SDK):

Android (Kotlin):


// Add dependencies in build.gradle
implementation 'com.stripe:stripe-android:20.8.0'

// In your activity/fragment
private fun setupStripe() {
    val paymentConfiguration = PaymentConfiguration.getInstance(applicationContext)
    paymentConfiguration.publishableKey = "pk_test_YOUR_PUBLISHABLE_KEY"
    
    val paymentMethodLauncher = PaymentMethodLauncher(
        this,
        paymentConfiguration.publishableKey,
        paymentConfiguration.stripeAccountId
    )
    
    // Fetch client secret from your server which creates the PaymentIntent
    val clientSecret = getClientSecretFromServer()
    
    paymentMethodLauncher.present(
        PaymentMethodLauncher.Configuration(
            clientSecret = clientSecret,
            merchantDisplayName = "Your Company Name"
        )
    )
}

iOS (Swift):


// Add to your Podfile
// pod 'Stripe'

import Stripe

class PaymentViewController: UIViewController {
    func setupStripe() {
        StripeAPI.defaultPublishableKey = "pk_test_YOUR_PUBLISHABLE_KEY"
        
        // Fetch client secret from your server which creates the PaymentIntent
        let clientSecret = getClientSecretFromServer()
        
        let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret)
        let paymentHandler = STPPaymentHandler.shared()
        
        paymentHandler.confirmPayment(paymentIntentParams, with: self) { (status, paymentIntent, error) in
            switch status {
            case .succeeded:
                print("Payment succeeded")
            case .canceled:
                print("Payment canceled")
            case .failed:
                print("Payment failed: (error?.localizedDescription ?? "")")
            @unknown default:
                print("Unknown status")
            }
        }
    }
}

extension PaymentViewController: STPAuthenticationContext {
    func authenticationPresentingViewController() -> UIViewController {
        return self
    }
}

 

Step 6: Process the Payment on Your Backend

 

Once you've collected payment details, process the payment on your backend:

Node.js:


async function processPayment(paymentMethodId, paymentIntentId) {
  try {
    // If you have a paymentIntentId, confirm it with the payment method
    if (paymentIntentId) {
      const paymentIntent = await stripe.paymentIntents.confirm(paymentIntentId, {
        payment\_method: paymentMethodId,
      });
      return paymentIntent;
    } else {
      // Create and confirm the payment intent in one step
      const paymentIntent = await stripe.paymentIntents.create({
        amount: 1000,
        currency: 'usd',
        payment\_method: paymentMethodId,
        confirm: true,
        return\_url: 'yourapp://return', // For mobile app deep linking
      });
      return paymentIntent;
    }
  } catch (error) {
    console.error('Error processing payment:', error);
    throw error;
  }
}

Python:


def process_payment(payment_method_id, payment_intent\_id=None):
    try:
        if payment_intent_id:
            # If you have a payment_intent_id, confirm it with the payment method
            payment\_intent = stripe.PaymentIntent.confirm(
                payment_intent_id,
                payment_method=payment_method\_id
            )
            return payment\_intent
        else:
            # Create and confirm the payment intent in one step
            payment\_intent = stripe.PaymentIntent.create(
                amount=1000,
                currency="usd",
                payment_method=payment_method\_id,
                confirm=True,
                return\_url="yourapp://return"  # For mobile app deep linking
            )
            return payment\_intent
    except Exception as e:
        print(f"Error processing payment: {e}")
        raise

 

Step 7: Handle Successful and Failed Payments

 

Implement handlers for successful and failed payments:

Node.js:


function handlePaymentOutcome(paymentIntent) {
  switch(paymentIntent.status) {
    case 'succeeded':
      // Payment successful - update your database, send confirmation, etc.
      console.log('Payment succeeded!');
      // updateOrderStatus(paymentIntent.metadata.order\_id, 'paid');
      // sendConfirmationEmail(paymentIntent.metadata.customer\_email);
      break;
    case 'requires\_action':
      // 3D Secure authentication or other action required
      console.log('Additional authentication required');
      // Return the client\_secret to your frontend to handle the authentication
      return {
        requires\_action: true,
        client_secret: paymentIntent.client_secret
      };
    case 'requires_payment_method':
      // Payment failed - inform user to try another payment method
      console.log('Payment failed, customer should try another payment method');
      // updateOrderStatus(paymentIntent.metadata.order_id, 'payment_failed');
      break;
    default:
      console.log(`Unhandled payment intent status: ${paymentIntent.status}`);
  }
  
  return {
    status: paymentIntent.status,
    id: paymentIntent.id
  };
}

Python:


def handle_payment_outcome(payment\_intent):
    if payment\_intent.status == 'succeeded':
        # Payment successful - update your database, send confirmation, etc.
        print('Payment succeeded!')
        # update_order_status(payment_intent.metadata.order_id, 'paid')
        # send_confirmation_email(payment_intent.metadata.customer_email)
        return {
            'status': payment\_intent.status,
            'id': payment\_intent.id
        }
    elif payment_intent.status == 'requires_action':
        # 3D Secure authentication or other action required
        print('Additional authentication required')
        # Return the client\_secret to your frontend to handle the authentication
        return {
            'requires\_action': True,
            'client_secret': payment_intent.client\_secret
        }
    elif payment_intent.status == 'requires_payment\_method':
        # Payment failed - inform user to try another payment method
        print('Payment failed, customer should try another payment method')
        # update_order_status(payment_intent.metadata.order_id, 'payment\_failed')
        return {
            'status': payment\_intent.status,
            'id': payment\_intent.id
        }
    else:
        print(f'Unhandled payment intent status: {payment\_intent.status}')
        return {
            'status': payment\_intent.status,
            'id': payment\_intent.id
        }

 

Step 8: Set Up Webhooks for Asynchronous Updates

 

Webhooks are crucial for receiving updates about payment statuses, especially for asynchronous payments:

Node.js (with Express):


const express = require('express');
const app = express();
const bodyParser = require('body-parser');

// This is your Stripe CLI webhook secret for testing your endpoint locally
const endpointSecret = 'whsec_YOUR_WEBHOOK\_SECRET';

app.post('/webhook', bodyParser.raw({type: 'application/json'}), (req, res) => {
  const sig = req.headers['stripe-signature'];

  let event;

  try {
    event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
  } catch (err) {
    console.log(`Webhook Error: ${err.message}`);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Handle the event
  switch (event.type) {
    case 'payment\_intent.succeeded':
      const paymentIntent = event.data.object;
      // Then define and call a function to handle the successful payment intent
      handleSuccessfulPayment(paymentIntent);
      break;
    case 'payment_intent.payment_failed':
      const failedPaymentIntent = event.data.object;
      // Then define and call a function to handle the failed payment intent
      handleFailedPayment(failedPaymentIntent);
      break;
    // ... handle other event types as needed
    default:
      console.log(`Unhandled event type ${event.type}`);
  }

  // Return a 200 response to acknowledge receipt of the event
  res.send();
});

app.listen(4242, () => console.log('Running on port 4242'));

function handleSuccessfulPayment(paymentIntent) {
  // Update your database
  // Fulfill the order
  // Send email confirmation to customer
  console.log(`PaymentIntent ${paymentIntent.id} succeeded`);
}

function handleFailedPayment(paymentIntent) {
  // Update your database
  // Alert the customer that their payment failed
  console.log(`PaymentIntent ${paymentIntent.id} failed`);
}

Python (with Flask):


from flask import Flask, request, jsonify
import stripe

app = Flask(**name**)

@app.route('/webhook', methods=['POST'])
def webhook():
    payload = request.data
    sig\_header = request.headers.get('Stripe-Signature')
    endpoint_secret = 'whsec_YOUR_WEBHOOK_SECRET'
    
    try:
        event = stripe.Webhook.construct\_event(
            payload, sig_header, endpoint_secret
        )
    except ValueError as e:
        # Invalid payload
        return jsonify({'error': str(e)}), 400
    except stripe.error.SignatureVerificationError as e:
        # Invalid signature
        return jsonify({'error': str(e)}), 400
    
    # Handle the event
    if event['type'] == 'payment\_intent.succeeded':
        payment\_intent = event\['data']\['object']
        handle_successful_payment(payment\_intent)
    elif event['type'] == 'payment_intent.payment_failed':
        payment\_intent = event\['data']\['object']
        handle_failed_payment(payment\_intent)
    # ... handle other event types as needed
    else:
        print(f'Unhandled event type {event["type"]}')
    
    return jsonify({'success': True})

def handle_successful_payment(payment\_intent):
    # Update your database
    # Fulfill the order
    # Send email confirmation to customer
    print(f"PaymentIntent {payment\_intent['id']} succeeded")

def handle_failed_payment(payment\_intent):
    # Update your database
    # Alert the customer that their payment failed
    print(f"PaymentIntent {payment\_intent['id']} failed")

if **name** == '**main**':
    app.run(port=4242)

 

Step 9: Implement Other Payment Flows (Optional)

 

Create a Subscription:


// Node.js
async function createSubscription(customerId, priceId) {
  try {
    const subscription = await stripe.subscriptions.create({
      customer: customerId,
      items: [{ price: priceId }],
      payment_behavior: 'default_incomplete',
      expand: ['latest_invoice.payment_intent'],
    });
    
    return {
      subscriptionId: subscription.id,
      clientSecret: subscription.latest_invoice.payment_intent.client\_secret,
    };
  } catch (error) {
    console.error('Error creating subscription:', error);
    throw error;
  }
}

Create a Payment Link:


// Node.js
async function createPaymentLink() {
  try {
    const paymentLink = await stripe.paymentLinks.create({
      line\_items: [
        {
          price: 'price_YOUR_PRICE\_ID',
          quantity: 1,
        },
      ],
      after\_completion: {
        type: 'redirect',
        redirect: {
          url: 'yourapp://success',
        },
      },
    });
    
    return paymentLink;
  } catch (error) {
    console.error('Error creating payment link:', error);
    throw error;
  }
}

 

Step 10: Testing Your Integration

 

To ensure your integration works correctly, perform these tests:

  1. Use Stripe's test cards to simulate different payment scenarios:
  • For successful payments: 4242 4242 4242 4242
  • For authentication required: 4000 0027 6000 3184
  • For payment declined: 4000 0000 0000 0002
  1. Test the full payment flow with each card type
  2. Verify webhooks are correctly received and processed using the Stripe CLI:

# Install Stripe CLI from https://stripe.com/docs/stripe-cli
stripe login
stripe listen --forward-to localhost:4242/webhook
  1. Check your application logs for any errors
  2. Verify database updates occur correctly after payment events

 

Step 11: Go Live with Your Integration

 

When you're ready to go live:

  1. Switch from test API keys to live API keys in your code
  2. Update webhook endpoints to use live webhook secrets
  3. Set up proper monitoring and error handling
  4. Implement comprehensive logging
  5. Ensure your privacy policy and terms of service cover payment processing
  6. If needed, set up Strong Customer Authentication (SCA) for European customers

 

Conclusion

 

You've now successfully set up Stripe integration without a traditional website. This approach works for mobile apps, IoT devices, backend systems, or any non-web application that needs payment processing capabilities. Remember to always keep your secret API keys secure and never expose them in client-side code.

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Book a Free Consultation

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with. They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

CPO, Praction - Arkady Sokolov

May 2, 2023

Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost. He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space. They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-code solutions.
We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 
This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022