Accept card payments

With Moov, you can build a checkout flow for accepting payments. For example, merchants selling goods or services through your platform may want to take card payments from their customers. In this guide, we cover the steps for setting up card acceptance.

For the purposes of this guide, we’ll assume the following key players:

  • Your Moov account: A platform facilitating the card payments
  • Merchant: The account taking card payments. This will be the destination for the Moov transfer.
  • Cardholder/Customer: The account being charged. This will be the source for the Moov transfer.

Install Moov.js

This guide covers using Moov Drops or Moov.js to onboard your merchants and cardholders, so you’ll need to get started by installing Moov.js. There are two different ways to install Moov.js. You can do a script based installation, or initialize the package via npm.

Include the script tag

1
2
3
4
<script type="text/javascript" src="https://js.moov.io/v1"></script>
<script>
  const moov = Moov(token)
</script>

Install via npm

1
npm i @moovio/moov-js
1
2
3
4
import { loadMoov } from '@moovio/moov-js';

const moovAccessToken = await fetch(...); // Use your server to get a Moov access token with appropriate scopes
const moov = await loadMoov(moovAccessToken);

Get your access token

Assuming you’ve already completed a basic setup of your Moov account, you’ll need to create an access token using your API key, which you can get from the Moov Dashboard. You will include a new access token as the Moov Drop onboarding.token whenever you onboard merchants or cardholders.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import { Moov, SCOPES } from '@moovio/node';

const moov = new Moov({
  accountID: "YOUR_MOOV_ACCOUNT_ID",
  publicKey: "PUBLIC_KEY",
  secretKey: "PRIVATE_KEY",
  domain: "YOUR_DOMAIN"
});

const scopes = [SCOPES.ACCOUNTS_CREATE];
try {
  const {token} = await moov.generateToken(scopes);
  // Do something with token
} catch(err) {
  // Handle any errors
}
1
2
3
4
5
curl -X POST "https://api.moov.io/oauth2/token" \
  -u "client_id:client_secret" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=client_credentials" \
  --data-urlencode  "scope=/accounts.write" \

Onboard your merchants

Card payments are modeled as a transfer from a source to a destination. In the card acceptance use case, the source is a cardholder and the destination is a merchant.

As the platform, you are facilitating card payments on behalf of your merchants. You will start by setting up Moov accounts for your merchants. Your merchants will be subject to identity verification. You have three different options for doing this:

Option 1: Collect the necessary information from the merchants via a pre-built onboarding UI component.

Option 2: Collect the necessary information from your merchants using Moov.js and your own UI.

Option 3: Use the API directly via our Node SDK. Note that when using our server-side Node SDK to onboard a sender, you can use the same code as you would when using Moov.js because the method names are the same.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// Onboarding Moov Drop

const onboarding = document.querySelector('moov-onboarding');
// After generating a token, set it on the onboarding element
onboarding.token = 'some-generated-token';

// Include your own accountID which can be found in the Moov Dashboard
onboarding.facilitatorAccountID = 'your-account-id';

// Transfers and collect-funds capabilities are needed for this flow.
onboarding.capabilities = ['transfers', 'collect-funds'];

// Funding will occur with a bank account
onboarding.paymentMethodTypes = ['bankAccount'];

// Verify bank account with microdeposits
onboarding.microDeposits = true;
// Follow the Onboarding Moov Drops and Plaid guides if linking bank accounts using Plaid.

// Open the onboarding flow when ready
onboarding.open = true;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// create Moov instance with generated token
const moov = Moov('some-generated-token');

// create Moov account, likely on form submit
moov.accounts.create({
	accountType: 'business',
	profile: {
		business: {}
	},
	capabilities: ['transfers', 'send-funds']
}).then((account) => {
	console.log(account);
}).catch((err) => {
	console.error(err);
});

// Generate a new token server-side with the accountID and update the token within your frontend app. You can chain methods together
moov.setToken('new-token');

// add a representative to account
moov.accounts.representatives.create({ 
	accountID: 'newly-created-accountID',
	representative: {}
});

// link a bank account
moov.accounts.bankAccounts.link({
	accountID: 'newly-created-accountID',
	bankAccount: {
		holderName: 'Name',
		holderType: 'business',
		accountNumber: '0004321567000',
		routingNumber: '123456789',
		bankAccountType: 'checking'
	}
}).then((bankAccount) => {
	// kick off micro-deposit verification
	moov.accounts.bankAccounts.startMicroDepositVerification({accountID, bankAccountID});
});

// Later, verify micro-deposits
moov.accounts.bankAccounts.completeMicroDepositVerification({accountID, bankAccountID, [12, 45]});

// Asychronously, the ach-debit-fund payment method will be created from this verified bank account. If you want to move funds out of the Moov wallet,  this bank account can be the destination of a wallet-to-bank transfer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// create Moov instance with generated token
const moov = Moov('some-generated-token');

// create Moov account, likely on form submit
moov.accounts.create({
	accountType: 'business',
	profile: {
		business: {}
	},
	capabilities: ['transfers', 'send-funds']
}).then((account) => {
	console.log(account);
}).catch((err) => {
	console.error(err);
});

// Generate a new token server-side with the accountID and update the token within your frontend app. You can chain methods together
moov.setToken('new-token');

// add a representative to account
moov.accounts.representatives.create({ 
	accountID: 'newly-created-accountID',
	representative: {}
});

// link a bank account
moov.accounts.bankAccounts.link({
	accountID: 'newly-created-accountID',
	bankAccount: {
		holderName: 'Name',
		holderType: 'business',
		accountNumber: '0004321567000',
		routingNumber: '123456789',
		bankAccountType: 'checking'
	}
}).then((bankAccount) => {
	// kick off micro-deposit verification
	moov.accounts.bankAccounts.startMicroDepositVerification({accountID, bankAccountID});
});

// Later, verify micro-deposits
moov.accounts.bankAccounts.completeMicroDepositVerification({accountID, bankAccountID, [12, 45]});

// Asychronously, the ach-debit-fund payment method will be created from this verified bank account. If you want to move funds out of the Moov wallet,  this bank account can be the destination of a wallet-to-bank transfer.

Onboard cardholders

Next, you’ll need to onboard your cardholders by:

  • Creating a Moov account for the cardholder
  • Making a server-side call to refresh the token with the new scopes for the accountID
  • Passing the token to the card link Drop and submitting it

Create a Moov account for the cardholder

Once the customer is prompted to pay, you can onboard them with Moov.js and create an account that will automatically request the transfers capability.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

const moov = Moov(token);

const account = await moov.accounts.create({
  "accountType": "individual",
  "profile": {
    "individual": {
      "name": "cardholder name",
      "phone": "123-456-7789"
      "email": "example@mail.com"
    }
  },
  "foreignId": "your-correlation-id",
  "capabilities": ["transfers"]
})

Generate the access token

Create an access token using your API key following the steps from above.

To link the customer’s card to their Moov account, you can use the pre-built Card Link Moov Drop in an HTML document as shown below and include the token you generated in the previous step:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20

<moov-card-link class="card-link"></moov-card-link>

<script>
  let moovCardLink = document.querySelector('moov-card-link');
  moovCardLink.accountID = 'accountID';
  moovCardLink.oauthToken = 'oauthToken';
  moovCardLink.onSuccess = (card) => {
    console.log('Card linked');
    // now show a success screen
  }
  moovCardLink.onError = (error) => {
    console.log('Error linking card');
    // now show an error screen
  }

  // call submit when button is pressed
  moovCardLink.submit();
</script>

Once the information has been submitted, Moov verifies the card details with card networks. You will not be responsible for storing or handling any of the card data, since all PII goes directly to Moov.

Facilitate card payments

All payments initially settle to a merchant’s Moov wallet account. Funds can then be moved to an external bank account using a wallet-to-bank transfer.

The customer’s card will be the source of funds, so we’ll need the card-payment payment method that was created after they linked their card. You can obtain the paymentmethodID from the payment methods endpoint by using the cardID as the sourceID.

Since the merchant’s wallet will be the destination of the funds, we will need the moov-wallet payment method. You can obtain the paymentmethodID by listing payment methods and finding the moov-wallet payment method associated with the merchant account.

To facilitate a card payment, create a transfer from the cardholder (source) to the merchant (destination).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

// Authenticate
import { Moov } from '@moovio/node';

const moov = new Moov({
  accountID: "YOUR_MOOV_ACCOUNT_ID",
  publicKey: "PUBLIC_KEY",
  secretKey: "PRIVATE_KEY",
  domain: "YOUR_DOMAIN"
});

// Server side Node SDK example 
try {
  const transfer = {
    source: { paymentMethodID: "customer's card-payment payment method" },
    destination: { paymentMethodID: "merchant's moov-wallet payment method" },
    amount: {
      value: 3000, // $30.00
      currency: "USD"
    },
    description: "Card payment example"
  };
  const { transferID } = moov.transfers.create(transfer);
} catch (err) {
  // ...
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

curl -X GET "https://api.moov.io/transfers" \
  -H 'Authorization: Bearer {token}' \
  -H "Origin: https://api.moov.io" \
  -H "X-Wait-For: rail-response" \
  --data-raw '{
    "source": {
			// Cardholder - paymentMethodType = card-payment
      "paymentMethodID": "UID",
    },
    "destination": {
		// Merchant - paymentMethodType = moov-wallet
      "paymentMethodID": "UID",
    },
    "amount": {
      "value": 3000, // $30.00
      "currency": "USD"
    },
    "description": "Card payment example"
  }'






  

Set statement descriptors

You can set the default statement descriptor for a merchant by using the account.settings.cardPayment.statementDescriptorfield in the PATCH accounts endpoint.

Optionally, source.cardDetails.dynamicDescriptor in the CREATE transfer endpoint allows a merchant to pass information to the card issuer at the time of transaction. The card issuer will then use this information to generate a description of the cardholder’s statement.

Set fees

If you want to monetize your payment flows, you can collect facilitator fees on card transfers. The amount specified will be deducted from the payment so that the destination receives the transfer amount net of fees. The fee amount is credited to your Moov wallet once the transfer is completed.

The FacilitatorFee field in the transfer request gives you the option to:

  • Implement your own customer-facing fee structure
  • Pass along your costs

For more information on facilitator fees and other fee structures, read our guide on fees here.

Track the status of a transfer

After creation, the overall transfer status will be pending until the funds are disbursed to the destination moov-wallet. The following business day, Moov receives settlement for the transactions from the card networks and then disburses the funds to the merchant’s wallet.

The X-Wait-For header waits until the card network has approved or declined the response and will provide you with a synchronous response in real-time. When you use this header, it will return the whole transfer object in the response. The platform should then look at cardDetails.status to ascertain the status of the payment.

  • confirmed = successfully authorized
  • failed = declined or failed for an unknown reason.

You can check on the status of the transfer at any time using the GET transfers endpoint. You can view detailed information about card processing in the cardDetails sub-object:

1
2
3
4
5
6
7
{
  "source": {
    "cardDetails": {
      "status": "confirmed"
    }
  }
}

You can also check on the status of the transfer by subscribing to the transfer.updatedwebhook.

For a sense of card-to-wallet transfer timing, see the diagram below.

Card to wallet

What’s next

If you’re interested in other use cases, you can dive into our guides: