# Payments

> Take payment for bookings and memberships, including 3-D Secure challenges.

HTML version: [Payments](https://support.gladstonesoftware.cloud/guides/payments)

# Payment API Guide

Use this guide to take a payment through the Gladstone Payments API, from selecting a membership to submitting the final payment request.

***

## 0. Prerequisites

You will need to pass in the  `Authorization` header for each call to Gladstone APIs, which is a *Bearer* token issued for your tenant. You can generate the token by following the [Authentication](authenticating-with-gladstone-apis.md) guide.

We have created a sample web application that demonstrates the recommended payment flow and provides a practical reference for integrating with the Gladstone Payments API. If you would like to explore the implementation or step through the payment flow, you can download the application using the link below.

[**Download**](https://2820540.fs1.hubspotusercontent-na1.net/hubfs/2820540/Gladestone%20Payment%20Example%20App/Gladstone-Payments-Example.zip)

**Example Only:** This application is provided as a reference to help you understand and test the payment flow. It is not intended to be used as-is in a production environment.

***

## 1. End-to-End Payments Flow summary

> 1. **Find a membership** (filter by type, duration, group).
> 2. **Generate a pricing quote** for the membership.
> 3. **Create a member and assign the membership** with the pricing quote.
> 4. **Collect card details securely** using Cardstream Hosted Payment Fields.
> 5. **Submit payment** via the `api/payment/pay` endpoint.
> 6. **Handle 3DS challenge** if required, and resubmit.

The remainder of this document walks through each of these steps in detail.

***

## 2. Membership Selection

### Step 2.1 - Get Member Types

```http
GET /api/configuration/tag-groups/{MEMBER_TYPES}
```

Retrieves available member categories (Adult, Senior, etc.).

👉 [Get tag group by ID or key](../reference/configuration/get-tag-group-by-id-or-key-with-optional-filtering.md).

**Implementation notes:** The tag keys you choose here from the response of the above call i.e., (`Tag.Key`) feeds directly into the memberships request in [step 2.4](#step-24-get-memberships-by-tag-group).

### Step 2.2 - Get Membership Durations

```http
GET /api/configuration/tag-groups/{MEMBERSHIP_DURATIONS}?filters={TYPE_ID}
```

Returns durations (Monthly, Annual, etc.) for the chosen type.

**Implementation notes:** Pass the (`Tag.Key`) from Step 2.1 via the `filters` query string so the result only contains valid durations. Example filter: `filters=MEMBER_TYPES:1`.

### Step 2.3 - Get Membership Types

```http
GET /api/configuration/tag-groups/{MEMBERSHIP_TYPES}
```

Retrieves membership style (Individual, Group, etc.).

**Implementation notes:** The tag group returned here is the same `tagGroupKey` that the memberships request in [step 2.4](#step-24-get-memberships-by-tag-group) requires.

### Step 2.4 - Get Memberships by Tag Group

```http
POST /api/memberships/membership-types-by-tag-group
```

`Input:` Selected member type, duration, membership type.

`Output:` Available Memberships.

👉  [Get membership types grouped by tag](../reference/joining/get-membership-types-grouped-by-tag.md).

**Implementation notes:**  The data returned from API response in steps 2.1, 2.2 and 2.3 would be used in this API call. Example payload looks similar to the following.

```json
{
  "tagKeys": ["MEMBER_TYPE_ADULT", "MEMBERSHIP_DURATION_MONTHLY", "MEMBERSHIP_GROUP_GYM"],
  "tagGroupKey": "SITE_DEFAULT",
  "siteId": "SITE01",
  "includeHeadlinePrice": true,
  "isWebBookable": true
}
```

The response includes each membership's `id`, `description`, `translations`, `price levels`, and headline pricing information `PriceBreakdown`. The next steps assume the chosen `id`, `orderKey`, and `priceLevelId` are retained for the pricing and membership-creation requests.

***

## 3. Pricing a Membership

```http
POST /api/pricing/memberships
```

Returns a pricing quote (signature + timestamp).

> ⚠️
>
> ###
>
> You must pass this object unchanged into the membership creation step below.

👉 [Pricing API docs](https://docs-openapi.gladstonesoftware.cloud/index.html?urls.primaryName=Pricing).

**Implementation notes:** Example payload looks similar to the following:

```json
{
  "siteId": "SITE01",
  "startDate": "2024-10-01T00:00:00Z",
  "members": [
    {
      "orderKey": 1,
      "memberId": null,
      "memberships": [
        {
          "id": "MEMBERSHIP_TYPE_KEY",
          "promotionCode": null
        }
      ]
    }
  ]
}
```

Response:

```json
{
  "timeStamp": "2024-10-01T12:00:00Z",
  "signature": "af05ec...",
  "siteId": "SITE01",
  "overview": {
    "now": { "amount": "45.00", "currency": "GBP" },
    "recurring": { "amount": "45.00", "currency": "GBP" }
  },
  "memberships": [
    {
      "orderKey": 1,
      "id": "MEMBERSHIP_TYPE_KEY",
      "pricing": {
        "now": { "totalPrice": { "amount": "45.00", "currency": "GBP" } },
        "recurring": { "totalPrice": { "amount": "45.00", "currency": "GBP" } }
      }
    }
  ]
}
```

`Creating Member with Membership` (next step) requires this object as it is. Do not modify `signature` or `timeStamp`.

***

## 4. Creating Member with Membership

```http
POST /api/memberships/add-members-with-memberships
```

`Input:` member details, membership details, and pricing quote.

`Output:` `memberIds` and `salesInvoiceIds` (needed for payment).

**Implementation notes:** Gladstone APIs validate the request against the following values.

* `siteId` - must match the pricing request.
* `primaryMember` - includes profile, address, contact details, bank details, user fields, and signed agreements.
* `linkedMembers` - optional array of dependants.
* `pricingQuote` - the exact response from [step 3](#3-pricing-a-membership).

Sample request skeleton:

```json
{
  "siteId": "SITE01",
  "primaryMember": {
    "orderKey": 1,
    "profile": {
      "titleId": "MR",
      "firstName": "Alex",
      "lastName": "Doe",
      "email": "alex@example.com",
      "dateOfBirth": "1990-01-01"
    },
    "addressDetails": {
      "address1": {
        "addressLine1": "1 Sample Street",
        "townCity": "Bristol",
        "postalCode": "BS1 1AA",
        "country": "GB",
        "isPrimary": true
      }
    },
    "contactDetails": {
      "mobile": { "number": "+441234567890", "isPrimary": true }
    },
    "optInMarketing": true,
    "signedAgreements": {
      "privacyPolicy": true,
      "termsAndConditions": true,
      "healthCommitmentStatement": true
    }
  },
  "linkedMembers": [],
  "pricingQuote": {}
}
```

Replace the empty `pricingQuote` object with the complete response from [step 3](#3-pricing-a-membership) before sending the request.

Response:

```json
{
  "membersCreated": [
    {
      "orderKey": 1,
      "memberId": 123456,
      "salesInvoiceIds": [78901]
    }
  ],
  "membershipsCreated": [{ "membershipId": 555 }],
  "timeStamp": "2024-10-01T12:05:00Z",
  "signature": "..."
}
```

Persist the `memberId` and every `salesInvoiceId`. Gladstone's payment APIs validate these values before contacting the payment provider, and you will use them in Step 6 below.

***

## 5. Collecting Payment Details

Card details must never reach your servers. Use Cardstream Hosted Payment Fields.

### Step 5.1 - Load Hosted Fields Library

```html
<script src="https://gateway.example.com/sdk/web/v1/js/hostedfields.min.js"></script>
```

Requires jQuery.

### Step 5.2 - Example Hosted Field HTML

```html
<form id="payment-form">
  <input type="hostedfield:cardNumber" name="card-number" />
  <input type="hostedfield:cardExpiryDate" name="expiry-date" />
  <input type="hostedfield:cardCVV" name="cvv" />
</form>
```

📚 [More Examples](https://guides.gitbook.io/integrationguide/references/code-references/example-library-code#hosted-payment-fields-library)

### Step 5.3 - Initialising the Form

```javascript
var form = new window.hostedFields.classes.Form(document.getElementById('payment-form'), {
  autoSetup: true,
  autoSubmit: false,
  merchantID: "<YOUR_MERCHANT_ID>",
  tokenise: '.add-to-token'
});

form.getPaymentDetails().then(result => {
  if (result.success) {
    const token = result.paymentToken;
    // send token to /api/payment/pay
  }
});
```

***

## 6. Submitting Payment

```http
POST /api/payment/pay
```

Input: `memberId`, `salesInvoiceIds`, and `paymentMethod` (with hosted-fields token or stored card).👉 [Pay for sales invoices](../reference/payments/pays-for-sales-invoices-using-a-specified-payment-provider-and-can-handle-paymen.md).

### Request contract

The payment request contract is:

| Property                                  | Notes                                                                           |
| ----------------------------------------- | ------------------------------------------------------------------------------- |
| `salesInvoiceIds`                         | Required array, duplicates get rejected.                                        |
| `memberId`                                | Required and validated against invoices returned by Payment Service.            |
| `paymentMethods`                          | Required list but Gladstone APIs currently restrict to a single payment method. |
| `paymentProvider`                         | Enum depicting available payment providers                                      |
| `amount`                                  | Must be >= outstanding invoice total.                                           |
| `paymentToken` or `cardId`                | For non-zero invoices, one of these must be provided.                           |
| `saveCardToWallet` / `saveRcpCard`        | Flags passed for tokenisation.                                                  |
| `threeDSecureChallengeReference/Response` | Only used for subsequent 3DS submissions.                                       |
| `threeDSecureChallengeOptionalField`      | Browser specifications and fields                                               |
| `deviceInformation`                       | Time zone, capabilities, and resolution for device fingerprinting.              |

Device information values can be found here: [https://guides.gitbook.io/integrationguide/gateway-functionality/features/advanced-data/device-information-fields](https://guides.gitbook.io/integrationguide/gateway-functionality/features/advanced-data/device-information-fields "https://guides.gitbook.io/integrationguide/gateway-functionality/features/advanced-data/device-information-fields")

Example request. Use the payment token created in Step 5 as the `paymentToken` value.

```json
{
  "salesInvoiceIds": [78901],
  "memberId": 123456,
  "paymentMethods": [
    {
      "paymentProvider": "GladstonePay",
      "amount": 45.00,
      "paymentToken": "TOKEN_FROM_CARDSTREAM",
      "saveCardToWallet": true,
      "saveRcpCard": false,
      "deviceInformation": {
        "timeZone": "0",
        "capabilities": "javascript",
        "screenResolution": "1170x2532"
      },
      "threeDSecureChallengeOptionalField": {
        "browserAcceptHeader": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        "browserLanguage": "en-GB",
        "browserTimeZone": "0",
        "browserUserAgent": "Mozilla/5.0 ...",
        "browserJavascriptEnabledVal": "true",
        "browserScreenHeight": "1170",
        "browserScreenWidth": "2532",
        "browserScreenColorDepth": "24"
      }
    }
  ]
}
```

### Response payload

A successful HTTP 200 response returns this shape:

```json
{
  "responseStatus": "PaymentSuccessful",
  "transactionId": "CARDSTREAM-TXN-ID",
  "message": null,
  "threeDSecureChallengeUrl": null,
  "threeDSecureChallengeFields": {}
}
```

Other statuses include `PaymentFailed`, `ThreeDSecureChallengeRequired`, `InvalidPaymentToken`, `ServiceUnavailable`, `InvalidRequest`, `InvalidConfiguration`, `InternalServerError`, and `ErrorDoNotRepeatTransaction`.

### Handling 3-D Secure (3DS)

1. When `responseStatus` is `ThreeDSecureChallengeRequired`, the HTTP status is still 200. `threeDSecureChallengeUrl` points to Cardstream's ACS and `threeDSecureChallengeFields` contains the form fields to POST (always includes `threeDSRef`).
2. The ThreeDSecure Challenge needs to be completed by the client side.
3. Once the challenge is done, add the returned value to the property threeDSecureChallengeResponse, and add the threeDSRef to the property threeDSecureChallengeReference. Post these in a second request to inform the payment provider of the result of the 3D Secure challenge via `api/payment/pay` endpoint

**Important:** Reuse the same `salesInvoiceIds` and `memberId` for every attempt. `InvoiceService` treats repeated calls as part of one transaction and will reject mismatched payloads.

***

## 7. Special Notes

* For **Recurring Card Payment (RCP)** memberships, set both `saveCardToWallet` and `saveRcpCard` to `true` so Gladstone API retains the token and marks it as the default card.
* Zero-priced invoices skip the provider call but still mark invoices as paid. This is logged by the Payment APIs and returns `PaymentSuccessful` with `transactionId = null`.
* `ErrorDoNotRepeatTransaction` indicates the provider collected funds but invoice processing failed. Investigate manually before retrying to avoid double-charging.
* `PaymentMethods` currently supports a single payment method.
* Use the wallet endpoints (Section 9) to promote a card saved during checkout to the default RCP card.

***

## 8. Example End-to-End Sequence

1. Fetch tag groups (member types, durations, membership types) from Configuration.
2. `POST /api/memberships/membership-types-by-tag-group` to display bookable memberships.
3. `POST /api/pricing/memberships` to obtain a signed pricing quote.
4. `POST /api/memberships/add-members-with-memberships` to create the member and receive `memberId`/`salesInvoiceIds`.
5. Capture hosted fields in browser, tokenise the card, and use returned Payment token.
6. `POST /api/payment/pay` with the token, invoice IDs, and device/browser metadata.
7. If 3DS is required, prompt the client to complete the challenge in the browser and the resubmit to the same endpoint with ThreeDsRef response.
8. (Optional) Add the card to the wallet using (`POST /api/members/{id}/wallet/card`) and set it as the default RCP card (`PUT /api/members/{id}/rcp-card`) as listed in Step 9.

***

## 9. Wallet & Recurring Card APIs

Following are some of the other endpoints that Gladstone APIs expose to interact with the wallet and handle recurring card payments.

| Endpoint                                                      | Purpose                                                                                                                                           |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /api/members/{memberId}/rcp-card`                        | Returns the current default recurring card (`rcpCardId`, `verified`).                                                                             |
| `PUT /api/members/{memberId}/rcp-card`                        | Sets the default recurring card: `{ "rcpCardId": "<CardStreamId>" }`.                                                                             |
| `GET /api/members/{memberId}/wallet/gladstonepay`             | Retrieves full wallet metadata (cards, tokens, RTUS status).                                                                                      |
| `POST /api/members/{memberId}/wallet/card`                    | Adds or verifies a card using `paymentToken`, optional 3DS fields, and device info. Returns `cardId`, `walletId`, and potential 3DS requirements. |
| `DELETE /api/members/{memberId}/wallet/gladstonepay/{cardId}` | Removes a stored card (ensure it is not the active RCP card).                                                                                     |

## 10. Payment Response Status Reference

* `PaymentSuccessful`
* `ThreeDSecureChallengeRequired`
* `PaymentFailed`
* `InvalidPaymentToken`
* `ServiceUnavailable`
* `InvalidRequest`
* `InvalidConfiguration`
* `InternalServerError`
* `ErrorDoNotRepeatTransaction`

`GladstonePay APIs` extend the list with 3DS-specific failures (`InvalidThreeDSRef`, `InvalidThreeDSDetails`, `InvalidThreeDSURL`, etc.). It is adviced to treat these as terminal errors that require a new challenge cycle or updated 3DS data.

***

For troubleshooting or Payments API related queries, please contact **Gladstone Support**.
