original source : https://youtu.be/RYiscsdICrs

node js를 기반으로 한 server를 이용한 연동 

위에서 body-parser는 post되어 들어온 데이터를 req.body를 통해 접근가능하게 한다.

위에서 env는 .env 화일에 저장되어 있는 여러 stripe key에 접근하기 위해 사용한다.(환경변수로저장한것에 접근하는걸로 이해)

위) 사용자를 stripe에 연결하려는 경우 사용자는 개발자가 제공한 ui (예를 들어 button)클릭해서 server에 접근하게 한다. 그러면  account , accountLink를 만들고 이른 사용자에게 되돌려 주는데 이안에는 링크주소, 유효기간이 들어가 데이터를 되돌려 준다. 그럼 사용자는 링크주소를 통해 stripe가 제공한 flow process를 거쳐 필요한 정보를 넣게 되고 마무리되면 success_url, failure_url로 사용자를 redirect하게 된다.

위) 미국 회사 사용자 가입시 입력해야 하는 기본 정보들이다. 이를 개발자가 미리 가지고 있다고 한다면 아래와 같이 prefill할수 있다.

original source : https://medium.com/@GaryHarrower/working-with-stripe-webhooks-firebase-cloud-functions-5366c206c6c

firebase functions 와 stripe webhook연결

Now lets head over to our terminal and create a new directory for our project.

$ mkdir stripe-webhooks
$ cd stripe-webhooks

We then need to install the firebase-tools package globally using NPM.

(아래는 firebase-tool설치)

$ npm install -g firebase-tools

Now we have firebase-tools installed, we should login to our firebase account and initiate a new project in our project directory. You will get a choice of which services you would like to use — you only need to select ‘Functions’ here for now.

(아래는 firebase 사용전 초기 작업)

$ firebase login
$ firebase init

Let’s move into the functions folder (created with firebase init) and make sure we have the latest version of firebase-functions and firebase-admin installed. firebase-functions is used for running the actual cloud functions and firebase-admin is used to access the Realtime Database.

$ cd functions/
$ npm install firebase-functions@latest firebase-admin@latest --save

Let’s create a new function called events to handle our endpoint. We’ll start off simple with a basic function that returns a string to let us generate the URL we need to supply our Stripe account with.

const functions = require('firebase-functions');exports.events = functions.https.onRequest((request, response) => {
 response.send("Endpoint for Stripe Webhooks!");
});

We can then deploy our function and get our endpoint (Function URL in output in screenshot).

firebase deploy --only functions

(아래는 stripe에 webhook 만드는 과정)

Now we have our Cloud Function URL, we can head over to the webhooks section of the Stripe dashboard. We then want to + Add Endpoint, and enter our URL into the URL field. You can select the types of events to be sent, but for now we will just stick to all event types.

Once you create the endpoint, take note of your ‘Signing secret’ — we’ll need that to verify the request send to our function.

While we’re in our Stripe Dashboard, let’s head over to the API Keys section to generate and take not of the API key we’re going to use.

You should create a restricted API key and only assign permissions you’re going to need in your firebase project. For example, if you’re only going to read customer objects, you can specify only Customers when creating the key.

Now we have our signing secret and our API key, let’s add them to our Firebase project as environment variables so we don’t need to check them in to any source control.

$ firebase functions:config:set 
   keys.webhooks="your_restricted_key"
   keys.signing="your_signing_key"

That’s our setup complete — We’re now ready to write some code! I’m going to be using examples from Firebase and Stripe, so if there’s anything you would like to dive deeper into, you can use the following links:

To start with, we’re going to need the Stripe NPM package, so let’s go ahead and install that:

(stripe 라이브러리 설치)

$ npm install --save stripe

We added our API keys to our Firebase config, so we can access them using functions.config() (For example: functions.config().keys.webhooks will return our keys.webhooks string we added).

We will then require the Stripe package in our functions index.js. We will also bring in our Signing key to our application (endpointSecret).

const functions = require(‘firebase-functions’);
const stripe = require(‘stripe’)(functions.config().keys.webhooks);
const endpointSecret = functions.config().keys.signing;exports.events = functions.https.onRequest((request, response) => {
 response.send(“Endpoint for Stripe Webhooks!”);
});

Note: Stripe marks a webhook as successful only when your function returns a success (2xx) response. If it receives anything else, such as a 400 or 500, then it marks it as failed, and will try again.

We can use our signing key to verify that a request has actually come from Stripe, and not an unauthorized attacker. The stripe package has a method (stripe.webhooks.constructEvent) which we can use to verify the request. We can also use a Try Catch to return an error if the request fails verification.

// Get the signature from the request header
let sig = request.headers["stripe-signature"];// Verify the request against our endpointSecret
let event = stripe.webhooks.constructEvent(request.rawBody, sig, endpointSecret);

Note: We need to use the original request body otherwise the verification will fail, so we must use Firebase Function’s request.rawBody, instead of the usual request.body.

As mentioned, we can wrap this in a Try Catch to catch any failed requests.

let sig = request.headers["stripe-signature"];try {
 let event = stripe.webhooks.constructEvent(request.rawBody, sig, endpointSecret);
} catch (err) {
 return response.status(400).end();
}

Now we have our valid events, let’s save them to our Firebase Realtime Database.

We can do this by using the firebase-admin database methods. We’re going to be using the .push() method to create a new entry in our database.

const admin = require('firebase-admin');
admin.initializeApp();...return admin.database().ref('/events').push(event)
 .then((snapshot) => {
   return response.json({ received: true, ref: snapshot.ref.toString() });
 })
 .catch((err) => {
   console.error(err);
   return response.status(500).end();
 });

Let’s break this code down a bit.

  • Ref is the path in the database we would like to save our new entry to. This can be whatever you like — i’ve chosen so save my events in the /events ref.
  • Push(event)- event is the variable we’re saving the response from the constructEvent method we called. This is an object with all of our event info
  • Response.json — We respond with a valid json object — this tells Stripe that the webhook event was received and we’re happy we’ve processed it, so mark it as complete.
  • Catch — In case something goes wrong while we’re saving the event to the database, we return an error 500 to tell Stripe that something went wrong. Stripe will then retry sending the event. There are ways you could incorporate this into the Try Catch we have, although I like having the differentiation of errors.

Now we should deploy our function again, then we can test it out.

$ firebase deploy --only functions

Let’s head back over to the Stripe Dashboard Webhooks area, select our endpoint where we can now ‘Send test webhook’.

Select an event type and hit ‘Send test webhook’ — all going well, we get a successful response, and our event is now saved in our database!

That’s it in terms of saving. Now you have endless possibilities of cool things to do with your data. For further reading, you could explore the different triggers Cloud Functions can use. You can run another function whenever anything is added to your database. The example below would run any time a new event is saved to our database. We could now check the event type, and if it’s a successful charge, update our Realtime database to increase our daily revenue on our live dashboard…

You can read more about database events here: https://firebase.google.com/docs/functions/database-events

I hope this was useful. Please leave any feedback or questions – I’m always learning and really appreciate any comments you may have.

You can find the completed code over on Github — https://github.com/GaryH21/Stripe-Webhooks-Tutorial

Happy building!

original source : https://stripe.com/docs/connect/collect-then-transfer-guide

Collect payments from customers and pay them out to sellers or service providers.

이글에서는 이해를 돕기 위해 집 renter와 rentee를 연결해주는 플랫폼을 만든다고 가정했다.

You can see a complete onboarding flow in action in our sample end-to-end Express integration.

Prerequisites

  1. Register your platform.
  2. Activate your account.
  3. Fill out your platform profile.

1. set up stripe library    -server side

# Install through pip
pip install --upgrade stripe

2. create connected account

When a user (seller or service provider 개발자가 만든 플랫폼을 이용해 영업할사람) signs up on your platform, create a user Account (referred to as a connected account) so you can accept payments and move funds to their bank account. Connected accounts represent your user in Stripe’s API and help facilitate the collection of onboarding requirements so Stripe can verify the user’s identity. In our home-rental example, the connected account represents the homeowner.

connected account를 만들수 있게 아래와 같은 버튼을 만들어 준다.

image

클리하면 아래와 같은 페이지로 이동하게 된다. 

image

2-1 Add an authentication button    -client side

Add an OAuth link to your website so the user can sign up for your platform:(위위 그림같은 버튼에 아래링크 넣는다.)

https://connect.stripe.com/express/oauth/authorize?client_id=ca_32D88BD1qLklliziD7gYQvctJIhWBSQ7&state={STATE_VALUE}&suggested_capabilities[]=transfers&stripe_user[email]=user@example.com
  • client_id     – This can be found in your platform settings. If you’re testing your integration, enable View test data in the Dashboard. (개발자만의 id라고 생각할수 있다. stripe가 connected account를 만들때 개발자가 누군지 알아야 하므로)
  • state     – Use this argument to prevent CSRF attacks. It should be a unique, not guessable value that’s generated and saved on your server. Stripe passes it back to your redirect after the user finishes the onboarding flow. (개발자가 임의로 만들어 제공하는 csrf token이며 stripe account생성작업을 마치고 돌아오는 user인지를 확인할때 사용한다.)
  • suggested_capabilities     – Use this argument only if you want to customize the capabilities for certain subsets of your connected accounts. Capabilities define what a connected account is allowed to do. In our home-rental example, we want the ability to process credit card and ACH payments directly from within the platform, then transfer some percentage of the funds to the homeowner. For this, we want the homeowner to have the transfers capability so they can receive transfers after fulfilling their onboarding requirements.
  • stripe_user     – You can use this argument to prefill any fields you already have so the user won’t need to provide that information again. In our home-rental example, we collected the homeowner’s email address when they signed up so this piece of information can be passed in the OAuth link. (위 그림 form 입력란에 미리 얻은 정보로 pre fill 할수 있게 한다.)

2-2 Save the account ID     -SERVER-SIDE

In platform settings, specify where the user should be redirected to after they’ve completed the OAuth flow with redirect_uri.

image

For example, if your redirect URI is https://www.example.com/connect/oauth, Stripe might redirect to https://www.example.com/connect/oauth?state=s_987654321&code=ac_123456789.

stripe connected account 를 만드는 작업을 한후 되돌아 올때 state (개발자가 만들어서 보낸 csrf token이 되돌아온다.)

If there’s an error, the URI contains the  error  and  error_description  parameters instead.

Write a  GET  handler to extract the parameters from the URI, verify the state against the state you saved on your server, and send the token to Stripe’s API:

import stripe
import json

# Using Flask.
from flask import (
    Flask,
    render_template,
    request,
)

app = Flask(__name__, static_folder=".",
            static_url_path="", template_folder=".")

@app.route('/', methods=['GET'])
def get_example():
  # Display landing page.
  return render_template('index.html')

# Set your secret key. Remember to switch to your live secret key in production!
# See your keys here: https://dashboard.stripe.com/account/apikeys
stripe.api_key = 'sk_test_4G17rywGpaql6aqpnHVjEdFc'

@app.route("/connect/oauth", methods=["GET"])
def handle_oauth_redirect():
  # Assert the state matches the state you provided in the OAuth link (optional).
  state = request.args.get("state")

  if not state_matches(state):
    return json.dumps({"error": "Incorrect state parameter: " + state}), 403

  # Send the authorization code to Stripe's API.
  code = request.args.get("code")
  try:
    response = stripe.OAuth.token(grant_type="authorization_code", code=code,)
  except stripe.oauth_error.OAuthError as e:
    return json.dumps({"error": "Invalid authorization code: " + code}), 400
  except Exception as e:
    return json.dumps({"error": "An unknown error occurred."}), 500

  connected_account_id = response["stripe_user_id"]
  save_account_id(connected_account_id)

  # Render some HTML or redirect to a different page.
  return json.dumps({"success": True}), 200

def state_matches(state_parameter):
  # Load the same state value that you randomly generated for your OAuth link.
  saved_state = "{{ STATE }}"

  return saved_state == state_parameter

def save_account_id(id):
  # Save the connected account ID from the response to your database.
  print("Connected account ID: ", id)

if __name__ == "__main__":
    app.run(port=4242)

response = stripe.OAuth.token(grant_type=“authorization_code”, code=code,) 

response[“stripe_user_id”]

위 코드와 살펴본다. 위 과정을 통해 stripe_user_id 를 얻을수 있고 나중을 위해 이를 database에 저장한다. stripe_user_id 는 acct_ 로 시작한다.  stripe_user_id 는 나중에 account로 자금을 이동시킬때 사용된다.

Make sure to save the stripe_user_id parameter that’s returned to your database. This is your connected account’s ID and begins with acct_. You will need it later to transfer funds to the account.

Step 2.3: Customize your signup form

By default, your Express signup form might look like this:

image

위와 같은 기본 폼을 변경하기 위해서는 platform settings 에서 아래와 같은 폼을 통해 한다.

image

3. accept a payment

Stripe Elements is a set of prebuilt UI components, like inputs and buttons, for building your checkout flow. If you’d rather not build your own payment form, consider Checkout, a Stripe-hosted page to accept payments for one-time purchases and subscriptions. (두가지 방법이 documentation 에 있다. 간단하게 stripe의 checkout를 이용하는 방법만을 여기서는 살펴본다. 다른 방법도 본래 doc에는 설명하고 있다. 아래와 같은 모양이 된다. 버튼을 클릭하면 stripe가 제공하는 page로 이동하게 된다.)

image

Step 3.1: Create a PaymentIntent   – SERVER-SIDE

Stripe uses a   PaymentIntent   object to represent your intent to collect payment from a customer, tracking charge attempts and payment state changes throughout the process.

Create a PaymentIntent on your server with an amount and currency. Always decide how much to charge on the server side, a trusted environment, as opposed to the client. This prevents malicious customers from being able to choose their own prices. (  PaymentIntent 생성시에 명확하게 금액과 currency를 명시해야 한다.)

# Set your secret key. Remember to switch to your live secret key in production!
# See your keys here: https://dashboard.stripe.com/account/apikeys
stripe.api_key = 'sk_test_4G17rywGpaql6aqpnHVjEdFc'

payment_intent = stripe.PaymentIntent.create(
  payment_method_types=['card'],
  amount=1000,
  currency='usd',
  application_fee_amount=123,
  transfer_data={
    'destination': '{{CONNECTED_STRIPE_ACCOUNT_ID}}',
  }
)

In our home-rental example, we want to build an experience where customers pay for rentals by using our platform, and where we pay homeowners for renting to customers. To set this experience up:

  • Indicate the rental is a destination charge with transfer_data[destination].
  • Specify how much of the rental amount will go to the platform with application_fee_amount.

When a rental charge occurs, Stripe transfers the entire amount to the connected account’s pending balance (transfer_data[destination]). Afterward, Stripe transfers the fee amount (application_fee_amount) to the platform’s account, which is the share of the revenue for facilitating the rental. Then, Stripe deducts the Stripe fees from the platform’s fee amount. An illustration of this funds flow is below:

transfer_data[destination] 는 connected account를 말한다. 위의 2-2에서 말한 stripe_user_id를 기입한다.  destination 이라고는 하지만 실제 자금의 흐름은 connected account로 들어갔다가 개발자 account로 수수료가 오고 이 금액에서 다시 stripe가 본인들의 수수료를 가져간다.

image

included in the returned PaymentIntent is a   client secret  , which is used on the client side to securely complete the payment process instead of passing the entire PaymentIntent object. There are different approaches that you can use to pass the client secret to the client side.  

return 된 PaymentIntent 안에는  client secret 이 들어 있으며 이를 client side에서 사용 payment 과정을 마무리하게 된다. PaymentIntent_obj.client_secret 이와 같이 client_secret을 얻을수 있다.

single page application과 server side rendering을 이용하는 방법이 doc에는 나와 있는데 일단 server side rendering 방법을 사용하기로 정함

server side rendering

You can embed it in a data attribute or hidden HTML element and then extract it with JavaScript in order to use it to complete payment.

checkout.html

<input id="card-name" type="text">
<!-- placeholder for Elements -->
<div id="card-element"></div>
<button id="card-button" data-secret="{{ client_secret }}">
  Submit Payment
</button>

app.py

@app.route('/checkout')
def checkout():
  intent = # ... Fetch or create the PaymentIntent
  return render_template('checkout.html', client_secret=intent.client_secret)

Step 3.2: Collect card details    -CLIENT-SIDE

You’re ready to collect card information on the client with Stripe Elements. Elements is a set of prebuilt UI components for collecting and validating card number, ZIP code, and expiration date.

A Stripe Element contains an iframe that securely sends the payment information to Stripe over a HTTPS connection. The checkout page address must also start with https:// rather than http:// for your integration to work.

You can test your integration without using HTTPS. Enable it when you’re ready to accept live payments. (실제에서는 https에서만 stripe element가 작동하게 된다. 즉 사용자가 카드번호넣고 클릭하는 버튼은 https에서만 작동 단 test시에는 상관없음)

checkout.html

<head>
  <title>Checkout</title>
  https://js.stripe.com/v3/
</head>

js 화일을 다운받아서 사용하면 안된다. 링크를 이용해야 한다.

script.js

// Set your publishable key: remember to change this to your live publishable key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
var stripe = Stripe('pk_test_내테스트키');
var elements = stripe.elements();

Add Elements to your payment page

payment button Element 는 payment form 안에 넣는다. Create empty DOM nodes (containers) with unique IDs in your payment form and then pass those IDs to Elements. js 에서 unique ID element를 찾아서 입력값에  접근 이를 stripe에 전송하게 된다.

checkout.html

<form id="payment-form">
  <div id="card-element">
    <!-- Elements will create input elements here -->
  </div>

  <!-- We'll put the error messages in this element -->
  <div id="card-errors" role="alert"></div>

  <button id="submit">Pay</button>
</form>

When the form above has loaded, create an instance of an Element and mount it to the Element container:      mount()를 통해 입력값에 접근

client.js

// Set up Stripe.js and Elements to use in checkout form
var style = {
  base: {
    color: "#32325d",
  }
};

var card = elements.create("card", { style: style });
card.mount("#card-element");

The  card  Element simplifies the form and minimizes the number of required fields by inserting a single, flexible input field that securely collects all necessary card and billing details. Otherwise, combine   cardNumber  ,   cardExpiry  , and   cardCvc   Elements for a flexible, multi-input card form.

Always collect a postal code to increase card acceptance rates and reduce fraud.

The single input card Element automatically collects and sends the customer’s postal code to Stripe. If you build your payment form with multi-input card Elements (cardNumber, cardExpiry, cardCvc), add a separate input field for the customer’s postal code.

간단하게 위와 같이 card만 생성하는 경우 자동으로 필요한 fields가 만들어지게 된다. 개발자가 각 항목을 정해서 만들수도 있다. card만 생성하는 경우 postal code를 입력하는 입력란이 자동으로 만들어 지는 데 개발자가 각 항목을 따로 만드는 경우 postal code입력란도 만들어야 한다.

For a full list of supported Element types, refer to our   Stripe.js reference   documentation. (다양한 입력 element를 제공하는데 doc을 확인하면 알수 있다.)

Elements validates user input as it is typed. To help your customers catch mistakes, listen to  change  events on the card Element and display any errors:

card.addEventListener('change', function(event) {
  var displayError = document.getElementById('card-errors');
  if (event.error) {
    displayError.textContent = event.error.message;
  } else {
    displayError.textContent = '';
  }
});

Postal code validation   depends on your customer’s billing country. Use our international test cards   to experiment with other postal code formats.

Step 3.3: Submit the payment to Stripe    -CLIENT-SIDE

Rather than sending the entire PaymentIntent object to the client, use its client secret from Step 3.1. This is different from your API keys that authenticate Stripe API requests.

The client secret should still be handled carefully because it can complete the charge. Do not log it, embed it in URLs, or expose it to anyone but the customer.

HTML + JS

To complete the payment when the user clicks, retrieve the client secret from the PaymentIntent you created in step 3.1 and call stripe.confirmCardPayment  with the client secret.

Pass additional billing details, such as the cardholder name and address, to the billing_details  hash. The card Element automatically sends the customer’s postal code information. However, combining cardNumber, cardCvc  , and   cardExpiry Elements requires you to pass the postal code to billing_details[address][postal_code].

client.js

var form = document.getElementById('payment-form');

form.addEventListener('submit', function(ev) {
  ev.preventDefault();
  stripe.confirmCardPayment(clientSecret, {
    payment_method: {
      card: card,
      billing_details: {
        name: 'Jenny Rosen'
      }
    }
  }).then(function(result) {
    if (result.error) {
      // Show error to your customer (e.g., insufficient funds)
      console.log(result.error.message);
    } else {
      // The payment has been processed!
      if (result.paymentIntent.status === 'succeeded') {
        // Show a success message to your customer
        // There's a risk of the customer closing the window before callback
        // execution. Set up a webhook or plugin to listen for the
        // payment_intent.succeeded event that handles any business critical
        // post-payment actions.
      }
    }
  });
});

stripe.confirmCardPayment may take several seconds to complete. During that time, disable your form from being resubmitted and show a waiting indicator like a spinner. If you receive an error, show it to the customer, re-enable the form, and hide the waiting indicator. 

stripe.confirmCardPayment 작업이 시간이 몇초 걸릴수 있으므로 기다리는 동안 payment클릭이 불가능하게 만들어서 사용자가 여러번 같은 payment를 하지 않게 한다

If the customer must authenticate the card, Stripe.js walks them through that process by showing them a modal. You can see an example of this modal experience by using the test card number   4000 0025 0000 3155   with any CVC, future expiration date, and postal code in the demo at the top of the page.

When the payment completes successfully, the value of the returned PaymentIntent’s   status   property is succeeded.   Check the status of a PaymentIntent in the Dashboard or by inspecting the status property on the object.   If the payment is not successful, inspect the returned error to determine the cause.

Step 3.4: Fulfillment     -SERVER-SIDE

이 단계에서는 payment가 완료된 상황에서 수행할 작업을 수행한다.

After the payment is completed, you’ll need to handle any fulfillment necessary on your end. A home-rental company that requires payment upfront, for instance, would connect the homeowner with the renter after a successful payment.

Do not rely on the redirect to the   success_url   param alone for fulfilling purchases as: (success_url parameters를 믿지 않는다.)

  • Malicious users could directly access the success_url without paying and gain access to your goods or services.
  • Customers may not always reach the success_url after a successful payment. It is possible they close their browser tab before the redirect occurs.

If you’re using Checkout, configure a   webhook endpoint  (for events from your account) in your dashboard.

server.py

import stripe
import json

# Using Flask.
from flask import (
    Flask,
    render_template,
    request,
    Response,
)

app = Flask(__name__, static_folder=".",
            static_url_path="", template_folder=".")

# Set your secret key. Remember to switch to your live secret key in production!
# See your keys here: https://dashboard.stripe.com/account/apikeys
stripe.api_key = 'sk_test_4G17rywGpaql6aqpnHVjEdFc'

# Uncomment and replace with a real secret. You can find your endpoint's
# secret in your webhook settings.
# webhook_secret = 'whsec_...'

@app.route("/webhook", methods=["POST"])
def webhook_received():
  request_data = json.loads(request.data)
  signature = request.headers.get("stripe-signature")

  # Verify webhook signature and extract the event.
  # See https://stripe.com/docs/webhooks/signatures for more information.
  try:
    event = stripe.Webhook.construct_event(
        payload=request.data, sig_header=signature, secret=webhook_secret
    )
  except ValueError as e:
    # Invalid payload.
    return Response(status=400)
  except stripe.error.SignatureVerificationError as e:
    # Invalid Signature.
    return Response(status=400)

  if event["type"] == "payment_intent.succeeded":
    payment_intent = event["data"]["object"]
    handle_successful_payment_intent(payment_intent)

  return json.dumps({"success": True}), 200

def handle_successful_payment_intent(payment_intent):
  # Fulfill the purchase.
  print('PaymentIntent: ' + str(payment_intent))

if __name__ == "__main__":
  app.run(port=4242)

Learn more in our   fulfillment guide for payments.

Testing webhooks locally

Testing webhooks locally is easy with the Stripe CLI.

  1. First, install the Stripe CLI on your machine if you haven’t already.
  2. Then, to log in run stripe login in the command line, and follow the instructions.
  3. Finally, to allow your local host to receive a simulated event on your connected account run stripe listen --forward-to localhost:{PORT}/webhook in one terminal window, and run stripe trigger payment_intent.succeeded (or trigger any other supported event) in another.


Step 3.5: Disputes

As the settlement merchant on charges, your platform is responsible for disputes. Make sure you understand the best practices for responding to disputes.

4 Complete and customize your integration

You now have a working integration. From your account dashboard, you can view an account and its balance.

Payouts

By default, any charge that you transfer to a connected account accumulates in the connected account’s Stripe balance and is paid out on a daily rolling basis. But, you can change the payout schedule if needed.

Testing

The Connect-specific testing resource provides tokens to help simulate flows for accounts and onboarding, payouts, and top-ups. To test your payments and disputes flow there are a number of test cards available to simulate payment outcomes.

Other resources

You have completed the steps required to build a working Connect integration. Based on your business needs, you can also do the following:

Manage connected accounts