How to Test Stripe Payments [With Code Samples]


Working on an ecommerce website or a marketplace app? Or maybe you need to test an Uber-like service or e-learning platform? These are different services, but they have one thing in common – you need to integrate a payment gateway like Stripe and, in most cases, test Stripe payments.

In this guide, I’m explaining how to test Stripe, what’s the right way to deal with its objects, and how to perform Stripe load and stability tests.

Let’s see how everything works.

How Does Stripe Work?

Stripe lets users pay for the content immediately: they don’t even need to register in your app or link their credit card with profiles. What they need to do is to enter cc credentials, and see what goes next:

  • Credit card credentials go to Stripe
  • Stripe tokenizes this info and returns the token to back-end
  • Back-end then makes a charge
  • All data is sent to Stripe, and Stripe shares this info with payment systems
  • Payment system responds to Stripe (if everything’s alright) or reports issues (if not)
  • Stripe then reports the state of the transaction
Stripe payment scheme

If the payment came through, users get access to the content. If it didn’t, they see an error message explaining what went wrong.

Now, let’s talk about Stripe features. I’m going to start with the most important ones – direct and destination charges.

Direct & Destination Charge

A direct charge means withdrawing money from the user’s account and sending them directly to your partner’s account.

Here’s how it works: imagine you’ve built an e-learning platform and hired educators to carry out classes. When a direct charge happens, students’ money goes directly to the teachers’ accounts. All the fees are also paid by the teacher, and your platform charges a fixed % from that sum.

Direct and Destination Charge

In case of a destination charge, it’s your platform that pays Stripe fees, and net worth is kept on the platform’s account. Money is first transferred to your platform’s Stripe account then to teachers’ accounts.

Authorization and Capture

Stripe also supports two-step payments, which means you can authorize a charge fist and capture it later.

Uber is the best-known example, I guess. When you book a ride, you see a rough cost of the trip, which will stay frozen on your credit card until you finish the ride. As soon as you confirm you’ve completed the ride, Uber calculates the final cost of the trip and charges it from the card.

Transfers, Subscriptions, Refunds

Stripe also allows transfers from the platform account to suppliers. For instance, tutors need to link their Stripe account to their profiles to get paid.

Stripe supports subscriptions too, allowing you to set different intervals, trial periods, and so on to configure your subscription.

Finally, if the user wants to cancel their order or subscription, Stripe lets you refund the amount (fully or partially) directly to their credit cards.

How to Test Objects in Stripe

Now, I suggest taking a more detailed look at the Stripe objects.

Source object

The source object stores a payment method, the one Stripe will use to charge the customer.

Here’s the checklist for the source object:

KeyValue
idstripe_id of added card
last4last 4 numbers of the card
brandcredit card company (AE, Visa, MasterCard, etc.)
exp_month, exp_yearcard expiration date
customertheir Stripe id

When you’re testing mobile apps, make sure that a payment method corresponds with the returned value. You can do it by checking last4 and exp_month/year parameters.

If the source object is linked to a specific user and you want to make sure it’s the right user, check the customer’s Stripe id.

JSON of the source object:

{
        "id": "card_1CboP4CLud4t5fBlZMiVrzBq",
        "object": "card",
        "address_city": null,
        "address_country": null,
        "address_line1": null,
        "address_line1_check": null,
        "address_line2": null,
        "address_state": null,
        "address_zip": null,
        "address_zip_check": null,
        "brand": "Visa",
        "country": "US",
        "customer": "cus_D1s9PQgvr6U46j",
        "cvc_check": "pass",
        "dynamic_last4": null,
        "exp_month": 4,
        "exp_year": 2024,
        "fingerprint": "soMjdt25OvcMcObY",
        "funding": "credit",
        "last4": "4242",
        "metadata": {},
        "name": null,
        "tokenization_method": null
      }

Customer object

KeyValue
idcustomer stripe_id
default_sourcestripe_id of default card
sourceslist of Sources
subscriptionslist of Subscriptions

Customer object stores all the payment methods, including the default one, and keeps the information about users and their subscriptions.

Stripe also stores user’s cc and the payment method they set as default – and you can charge customers based on this info. Same with subscriptions: Stripe charges subscription fees automatically.

In Stripe, you can create webhooks for any event – that’s how you find out when the customer was charged or when an error showed up.

{
  "id": "cus_D1s9PQgvr6U46j",
  "object": "customer",
  "account_balance": 0,
  "created": 1528717303,
  "currency": null,
  "default_source": "card_1CboP4CLud4t5fBlZMiVrzBq",
  "delinquent": false,
  "description": null,
  "discount": null,
  "email": null,
  "invoice_prefix": "4A178DE",
  "livemode": false,
  "metadata": {},
  "shipping": null,
  "sources": {
    "object": "list",
    "data": [
      {
        "id": "card_1CboP4CLud4t5fBlZMiVrzBq",
        "object": "card",
        "address_city": null,
        "address_country": null,
        "address_line1": null,
        "address_line1_check": null,
        "address_line2": null,
        "address_state": null,
        "address_zip": null,
        "address_zip_check": null,
        "brand": "Visa",
        "country": "US",
        "customer": "cus_D1s9PQgvr6U46j",
        "cvc_check": "pass",
        "dynamic_last4": null,
        "exp_month": 4,
        "exp_year": 2024,
        "fingerprint": "soMjdt25OvcMcObY",
        "funding": "credit",
        "last4": "4242",
        "metadata": {},
        "name": null,
        "tokenization_method": null
      },
      {
        "id": "card_1CcC3uCLud4t5fBlW2UMknUW",
        "object": "card",
        "address_city": null,
        "address_country": null,
        "address_line1": null,
        "address_line1_check": null,
        "address_line2": null,
        "address_state": null,
        "address_zip": null,
        "address_zip_check": null,
        "brand": "Visa",
        "country": "US",
        "customer": "cus_D1s9PQgvr6U46j",
        "cvc_check": "pass",
        "dynamic_last4": null,
        "exp_month": 4,
        "exp_year": 2024,
        "fingerprint": "soMjdt25OvcMcObY",
        "funding": "credit",
        "last4": "4242",
        "metadata": {},
        "name": null,
        "tokenization_method": null
      }
    ],
    "has_more": false,
    "total_count": 2,
    "url": "/v1/customers/cus_D1s9PQgvr6U46j/sources"
  },
  "subscriptions": {
    "object": "list",
    "data": [],
    "has_more": false,
    "total_count": 0,
    "url": "/v1/customers/cus_D1s9PQgvr6U46j/subscriptions"
  }
}

Charge object

KeyValue
idcharge stripe_id
amountamount paid, ¢
amount_refundedamount refunded, ¢
customercustomer_id of the payer
capturedtrue – payment processed, false – authorized
destinationstripe account of payee

Let’s take a more detailed look at these keys.

  • amount – in cents, euro cents, and so on. Always check the amount customer was charged for.
  • amount_refunded – you’ll see a value different from 0 only if the charge (or a part of it) was refunded to the user
  • customer – customer’s id
  • captured – shows transaction status: held on the user’s card or charged
  • destination – stores the Stripe account of the user you’ve sent money to during a destination charge.
"fingerprint": "soMjdt25OvcMcObY",
    "funding": "credit",
    "last4": "4242",
    "metadata": {},
    "name": null,
    "tokenization_method": null
  },
  "source_transfer": null,
  "statement_descriptor": null,
  "status": "succeeded",
  "transfer_group": null
}

Refund object

KeyValue
idrefund stripe_id
amountpayment amount, ¢
statussuccess/pending/failed

A refund object could be inbuilt in the charged object if the payment or a part of it was refunded to the user.

{
  "id": "re_1CcY10CLud4t5fBlN23KtYq7",
  "object": "refund",
  "amount": 999,
  "balance_transaction": "txn_1CcY10CLud4t5fBlhlmzzJuK",
  "charge": "ch_1CcD7dCLud4t5fBlC1srZNIB",
  "created": 1528892634,
  "currency": "usd",
  "metadata": {},
  "reason": null,
  "receipt_number": null,
  "status": "succeeded"
}

Transfer object

KeyValue
idtransfer_id
amountpayout amount, ¢
destinationlinked account (payee)
reversedfalse – transaction, true – funds reversal
reversalslist of reverse transfer objects

Keeps information about transfers from the platform balance to other accounts (to your teacher, if you have an e-learning platform).

All money transactions should be loginized in the database. This makes testing much easier: if QA engineers see the transfer id, they can go to Stripe and look through the parameters.

Let’s see how it works on examples. I’ll stick to the idea of the e-learning platform and teachers who are your payees.

  • amount – sum paid to the teacher
  • destination – Stripe account of the teacher
  • reversed – shows a false value if the transaction came through and a true value if it’s reversed (when you need to cancel a transaction)
  • reversals – keeps the list of objects if any part of your transfer was reversed
{
  "id": "tr_1CcApyCLud4t5fBlZyx5mEPI",
  "object": "transfer",
  "amount": 250,
  "amount_reversed": 0,
  "balance_transaction": "txn_1CcApyCLud4t5fBlfA5cgXBz",
  "created": 1528803538,
  "currency": "usd",
  "description": null,
  "destination": "acct_18bAS3KcT341ksb9",
  "destination_payment": "py_1CcApyKcT341ksb9VawxIJdS",
  "livemode": false,
  "metadata": {},
  "reversals": {
    "object": "list",
    "data": [],
    "has_more": false,
    "total_count": 0,
    "url": "/v1/transfers/tr_1CcApyCLud4t5fBlZyx5mEPI/reversals"
  },
  "reversed": false,
  "source_transaction": null,
  "source_type": "card",
  "transfer_group": null
}

Balance Transaction object

KeyValue
idrefund stripe_id
amountpayment amount, ¢ (mind +/- signs)
available_onday when money will be available for a payee
feeStripe fee
fee_detailslist of fee object
netnet income/expenditure
statuscurrent status of the transaction
typetransaction type

Balance transaction stores details about any changes to your platform’s balance. You don’t actually need to test this object, but you should know where fees come from.

  • amount – in cents; sometimes followed by + or – which shows the process of sending and receiving funds respectively
  • available_on – shows when the money send to partners (teachers) will be available for them
  • fee – amount of the Stripe fee
  • fee_details – list of fee objects describing why this fee was charged
  • net – net income
  • status – status of operation success.
  • type – stores a type of the object (charge, refund, or transfer).

Balance transaction (transfer):

{
  "id": "txn_1CcApyCLud4t5fBlfA5cgXBz",
  "object": "balance_transaction",
  "amount": -250,
  "available_on": 1528803538,
  "created": 1528803538,
  "currency": "usd",
  "description": null,
  "exchange_rate": null,
  "fee": 0,
  "fee_details": [],
  "net": -250,
  "source": "tr_1CcApyCLud4t5fBlZyx5mEPI",
  "status": "available",
  "type": "transfer"
}

Balance transaction (charge):

{
  "id": "txn_1CbrRTCLud4t5fBlhRfMLdq1",
  "object": "balance_transaction",
  "amount": 10000,
  "available_on": 1529280000,
  "created": 1528728983,
  "currency": "usd",
  "description": "Charge user [email protected] for instructor [email protected] lesson id: 77",
  "exchange_rate": null,
  "fee": 320,
  "fee_details": [
    {
      "amount": 320,
      "application": null,
      "currency": "usd",
      "description": "Stripe processing fees",
      "type": "stripe_fee"
    }
  ],
  "net": 9680,
  "source": "ch_1CbrP3CLud4t5fBlztHMxVzv",
  "status": "pending",
  "type": "charge"
}

Subscription object

KeyValue
idsubscription stripe_id
application_fee_percent% charged for subscription
billingautomatic charge/sending invoice
billing_cycle_anchortime of the next subscription cycle
current_period_start
current_period_end
subscription timeframes
planset of rules for subscription: amount, interval, trial days
  • application_fee_percent – % of the full amount the platform charges after users get a subscription. The rest of the money goes to the content owner.
  • billing – shows how the billing process is organized: automatically or manually (via invoices)
  • billing_cycle_anchor – contains the due date of the next payment (if the customer wants to keep their subscription)
  • current_period_start & current_period_end – subscription validity dates
  • plan – includes a set of rules like the amount users should pay for the subscription, its interval, number of trial days, and so on.
{
  "id": "sub_D2JskPBqcW24hu",
  "object": "subscription",
  "application_fee_percent": null,
  "billing": "charge_automatically",
  "billing_cycle_anchor": 1528820423,
  "cancel_at_period_end": false,
  "canceled_at": null,
  "created": 1528820423,
  "current_period_end": 1531412423,
  "current_period_start": 1528820423,
  "customer": "cus_D2Jsi3JgT5zPh1",
  "days_until_due": null,
  "discount": null,
  "ended_at": null,
  "items": {
    "object": "list",
    "data": [
      {
        "id": "si_D2Js7N4mYxzAaY",
        "object": "subscription_item",
        "created": 1528820424,
        "metadata": {
        },
        "plan": {
          "id": "ivory-express-917",
          "object": "plan",
          "active": true,
          "aggregate_usage": null,
          "amount": 999,
          "billing_scheme": "per_unit",
          "created": 1528819224,
          "currency": "usd",
          "interval": "month",
          "interval_count": 1,
          "livemode": false,
          "metadata": {
          },
          "name": "Ivory Express",
          "nickname": null,
          "product": "prod_D2JYysdjdQ2gwT",
          "statement_descriptor": null,
          "tiers": null,
          "tiers_mode": null,
          "transform_usage": null,
          "trial_period_days": null,
          "usage_type": "licensed"
        },
        "quantity": 1,
        "subscription": "sub_D2JskPBqcW24hu"
      }
    ],
    "has_more": false,
    "total_count": 1,
    "url": "/v1/subscription_items?subscription=sub_D2JskPBqcW24hu"
  },
  "livemode": false,
  "metadata": {
  },
  "plan": {
    "id": "ivory-express-917",
    "object": "plan",
    "active": true,
    "aggregate_usage": null,
    "amount": 999,
    "billing_scheme": "per_unit",
    "created": 1528819224,
    "currency": "usd",
    "interval": "month",
    "interval_count": 1,
    "livemode": false,
    "metadata": {
    },
    "name": "Ivory Express",
    "nickname": null,
    "product": "prod_D2JYysdjdQ2gwT",
    "statement_descriptor": null,
    "tiers": null,
    "tiers_mode": null,
    "transform_usage": null,
    "trial_period_days": null,
    "usage_type": "licensed"
  },
  "quantity": 1,
  "start": 1528820423,
  "status": "active",
  "tax_percent": null,
  "trial_end": null,
  "trial_start": null
}

Load and Stability Testing

Finally, you may want to know how many transactions Stripe can process and if there’s any limit set. I recommend using GMeter to test these parameters.

Let’s see how you can test it:

  1. Create an event that launches the payment logic on the server-side.
  2. Check transaction logs in your database and its status using GMeter tools. Don’t forget that all transactions must be loginized in the database.
  3. Emulate these actions from n-users and specify the number of successful operations per specific period of time (like ten operations in one minute).

This way, you will see whether you need to optimize the payment logic, or it already works well.

That will be all! As you see, Stripe testing isn’t as hard as it may seem. You just need to make sure you understand how Stripe works and how to handle its objects the right way.


Leave a Reply