Level 2 and Level 3 Pricing
Note
Our Clear and Link integrations offer Level 2/Level 3 (L2/L3) processing for MasterCard and Visa cards issued in the US & used in card-not-present transactions.
L2/L3 processing provides card holders with better identifiable transaction information, and helps merchants reduce the number of Disputes they receive. Additionally, platforms using revenue share and/or IC+ with us will see lower processing costs for eligible transactions.
L2/L3 processing is a protocol for providing better reporting on special business-to-business (B2B) and business-to-government (B2G) cards including the Visa Purchasing Card, several different MasterCard Corporate/Purchasing cards, and a few other businesses, government, and commercial cards. These cards are intended to streamline accounting processes and improve transaction reporting for large businesses and organizations by collecting more data at the time of transaction. This additional data results in a more secure transaction, and thus qualifies for lower processing costs.
L2 data is primarily tax data related to the transaction. Business cards can only qualify for L2 rates.
L3 data includes the L2 tax data, in addition to line item details.
Platforms should consider implementing L2/L3 processing if a portion of their transactions are consistently processed with commercial cards. Typically, these cards incur high interchange costs, and utilizing L2/L3 processing can help platforms save costs in addition to providing better reporting for cardholders. Additionally, platforms will see a higher rate of L2/L3 eligibility if their merchants already tend to collect the data required for L2/L3.
L2/L3 processing moves through the following steps:
- Platforms and/or merchants on IC+ and revenue share qualify for immediate lower transaction fees via lower interchange rates when collecting and passing L2/L3 data.
- Platforms and/or merchants on blended rates qualify for reactive pricing adjustments based on historical savings from L2/L3 transactions.
- All platforms and merchants will see a decrease in chargebacks for L2/L3 transactions as a result of more security upfront and better descriptors passed on to card holders.
- L2/L3 data is reported back to Visa and Mastercard, allowing businesses to keep track of their spending and set restrictions on a company credit card.
Current L2/L3 Support |
---|
|
Additional support will be added in the future and documented here.
Set Up
At a high-level, the L2/L3 process is as follows:
- Work with your account executive, integration team, or technical account manager to get enabled for L2/L3 processing.
- Identify merchants on your platform who already utilize most or all of the L2/L3 data points.
- Send L2/L3 data on all transactions for that merchant using Orders and Items resources.
- For auth/cap transactions, send L2/L3 data in the initial auth (
POST /payments
) and update data as needed before the capture leg. Read more about this in the Capture Payments Later section below. - The credit networks and WePay will evaluate each transaction for L2/L3 eligibility, so it is best practice to send L2/L3 data on all your merchants' transactions.
- WePay and the card networks will evaluate each transaction's eligibility as they are received.
- Partners on IC+ pricing will receive IC+ reports which will also reflect any merchants' adjusted pricing.
Define Items
Items are structurally tied to an Account, so you have two options for creating items. You can create Items and pass them inline when creating or updating an Account. Alternatively, you can create Items by sending a POST to the Items endpoint. If you use this method, you must pass theaccount_id
, so first create an Account.You can also attach rBits to Items.
Create Orders
Orders are structured objects to help you pass risk information to us. Conceptually, Orders are a superset of Items, and can also contain rBits.
Orders are roughly equivalent to meta data about a checkout, like a payment receipt.
Orders are flexible - they could refer to Items in them, and some Items could just be text. Orders could also be an invoice, which can be tied to the payment, but it is not necessary to be. They are top level items.
There's an order of operations element to this - we'd like to get risk information before a payment is processed. You could also call a separate order and a separate payment after checkout, but you can also do this inline.
Note
POST /payments
, integer-type parameters in Order and Items should be sent in their lowest denomination. This means that an amount of $10.00 should be sent as 1000
.Compile L2 Data
Begin constructing aPOST /orders
request with the following L2 data points:Order Parameter | Visa | MasterCard | Description |
---|---|---|---|
customer_reference_number | optional | required | Purchase order or other number used by the merchant's customer to track the order. Note: This value cannot be empty or 0. |
tax_amount | required | required | Amount of sales tax assessed to the transaction. Required for Visa Level 2 processing, the sales tax amount must be between 0.1% and 22% of the purchase price or the transaction does not qualify for the best possible interchange rate. Required for Mastercard Level 2 processing, the sales tax amount must be between 0.1% and 30% of the purchase price or the transaction does not qualify for the best possible interchange rate. Conditional MasterCard MCCs If your merchant has one of the following MCCs, then |
Optional fields are recommended in order to provide the best reporting for payers and for the best chances of qualifying for L2 rates on eligible cards.
If required or optional fields are not provided or incorrectly sent, the transaction will be downgraded to level 1 interchange rate for eligible cards.
L2 data is primarily about overall tax information, so L2 data is only found in the Orders resource.
Note
null
.Your request should look something like this:
curl -X POST \
--url 'https://stage-api.wepay.com/orders' \
-H 'Accept: application/json'\
-H 'App-Id: {your-app-id}'\
-H 'App-Token: {your-app-token}'\
-H 'Api-Version: 3.0'\
-H 'Content-Type: application/json' \
--data-raw '{
"account_id": "{merchant's-account-id}",
"short_description": "Order from Mrs. Berkenshire for one elite mop on Oct. 31 1999",
"type": "goods",
"customer_reference_number": "order-A78240981",
"tax_amount": 0
}'
Important:
account_id
, short_description
, and type
were all included in this example because they are required parameters.If this data is not provided or incorrectly sent, then the transaction will be downgraded to level 1 for eligible cards.
The API response will look like this, with values dependent on the values you sent in the request:
{
"create_time": 1583371282,
"reference_number": null,
"short_description": "Order from Mrs. Berkenshire for one elite mop on Oct. 31 1999",
"long_description": null,
"type": "goods",
"delivery_type": null,
"buyer": null,
"status": null,
"customer_reference_number": "order-A78240981",
"tax_amount": 0,
"alternate_tax_amount": 0,
"alternate_tax_id": null,
"vat_amount": 0,
"vat_rate": 0.0,
"discount_amount": 0,
"duty_amount": 0,
"freight_amount": 0,
"grand_total_amount": 0,
"ship_to_address": null,
"ship_from_address": null,
"shipping_status": null,
"service_address": null,
"notes": null,
"terms": null,
"line_items": null,
"currency": null,
"owner": {
"id": "{merchant's-account-id}",
"path": "/accounts/{merchant's-account-id}",
"resource": "accounts"
},
"id": "0f0a0d95-2f2c-43eb-a9d1-d8e01aa40abf",
"path": "/orders/0f0a0d95-2f2c-43eb-a9d1-d8e01aa40abf",
"resource": "orders",
"state": "active",
"api_version": "3.0"
}
To use L3 data, continue to the next section below.
If you're not sending L3 data, you'll use the Order'sid
to attach the L2 data to the transaction itself. Find out how in the Create an L2 or L3 Payment section.Compile L3 Data
Begin constructing aPOST /orders
request with the following L3 data points:Order Parameter | Visa | MasterCard | Description |
---|---|---|---|
customer_reference_number | optional | required | Purchase order or other number used by the merchant's customer to track the order. Note: This value cannot be empty or 0. |
tax_amount | optional | required | Amount of sales tax assessed to the transaction. Conditional MasterCard MCCs If your merchant has one of the following MCCs, then |
discount_amount | optional | optional | The total amount of discount applied to the order by the transaction by the merchant. If no discount was applied, then send 0 .Note: Cannot contain spaces or be null. |
duty_amount | optional | optional | Total charges for any import and/or export duties included in this transaction. If no duties, then send 0 .Note: The value cannot contain spaces and cannot be null. |
freight_amount | optional | optional | Total freight or shipping and handling charges. If no freight charges, then send 0 .Note: The value cannot contain spaces and cannot be null. |
ship_to_address.postal_code | optional | optional | If this field is not supplied then the ship to zip, from the ship to address record is used. If neither is supplied, the merchant location postal code is used. |
ship_to_address.country | optional | optional | The alpha ISO country code of the country to which the goods were shipped. |
ship_from_address.postal_code | optional | optional | The zip/postal code of the location from which the goods were shipped. |
alternate_tax_id | optional | optional | Tax ID number for the alternate tax associated with this transaction. |
alternate_tax_amount | optional | optional | Total amount of alternate tax associated with this transaction. |
Parameter under line_items object | Visa | MasterCard | Description |
discount_amount | optional | optional | Amount of discount applied to the line item. If no discount was applied, then send 0 .Note: Cannot contain spaces or be null. |
commodity_code | required | optional | The commodity code used to classify the item purchased. Note: The value cannot contain spaces, or be null or 0. Find the most recent commodity codes defined by United Nations Standard Products and Services Code here. Note that PDF downloads (towards the bottom of the page) are free to download. |
description | required | required | Text description of the item purchased. Note: The value cannot contain spaces, or be null or 0. |
item.product_code | optional | required | Product code of the item purchased. These should be defined by merchants per their requirements. Note: The value cannot contain spaces, or be null or 0. Click for a non-exhaustive list of example product codes for Fleet transactions that are non-fuel and occur at a fuel location:
|
quantity | required | required | The number of a given item included in the associated Order. Note: The value cannot contain spaces, or be null or 0. |
tax_amount | optional | required | Tax amount for this item. Note: The value cannot contain spaces or be null. If no tax applied, send 0. |
tax_rate | conditional | required | Tax rate applied to this item. Note: The value cannot contain spaces or be null. If no tax applied, send 0. |
total_amount | required | required | The amount of the item calculated as price times quantity. Price in this equation should include freight, duty, & tax. Note: The value cannot contain spaces, or be null or 0. |
unit_of_measure | required | required | The unit of measure, or unit of measure code used for this item. Note: The value cannot contain spaces, or be null or 0. Click to see codes for units of measurement used in international trade. The code is the acronym in capital letters (e.g. EA, ACR)
|
unit_price | optional | optional | The cost of a single unit without any tax, freight, or duties. Note: The value cannot contain spaces, or be null or 0. |
gross_net_indicator | optional | optional | Indicates whether tax amount is included in item amount. Valid values: Y - item amount includes tax amountN - item amount does not include tax amount |
discount_indicator | optional | optional | Indicates whether the amount is discounted. Valid values: Y - amount is discountedN - amount is not discounted |
tax_type_applied | optional | optional | Type of Tax being applied. |
Optional fields are recommended in order to provide the best reporting for payers, and for the best chances of qualifying for L3 rates on eligible cards.
If required and optional fields are not provided or incorrectly sent, the transaction will be downgraded to level 2 or level 1 interchange rate for eligible cards.
Note
null
.POST /orders
request should look like for an L3 transaction:curl -X POST \
--url 'https://stage-api.wepay.com/orders' \
-H 'Accept: application/json'\
-H 'App-Id: {your-app-id}'\
-H 'App-Token: {your-app-token}'\
-H 'Api-Version: 3.0'\
-H 'Content-Type: application/json' \
--data-raw '{
"account_id": "{merchant's-account-id}",
"short_description": "Order from Mrs. Berkenshire for one elite mop on Oct. 31 1999.",
"type": "goods",
"customer_reference_number": "order-A78240981",
"tax_amount": 0,
"discount_amount": 0,
"duty_amount": 0,
"freight_amount": 1000,
"alternate_tax_id": "EIN",
"alternate_tax_amount": 0,
"ship_to_address": {
"country": "US",
"postal_code": "90210"
},
"ship_from_address": {
"country": "US",
"postal_code": "94101"
},
"line_items": [
{
"currency": "USD",
"quantity": 2,
"commodity_code": "25101803",
"discount_amount": 0,
"description": "Elite mop with green handle and white mop head.",
"tax_amount": 0,
"tax_rate": 0,
"total_amount": 2000,
"unit_of_measure": "ITM",
"gross_net_indicator": "Y",
"discount_indicator": "N",
"tax_type_applied": "state sales tax",
"unit_price": 2000,
"item": {
"account_id": "{merchant's-account-id}",
"product_code": "E-M1",
"product_name": "Elite Basic Mop"
}
}
]
}'
Important:
account_id
, short_description
, type
, line_items.currency
, line_items.item.account_id
, & line_items.item.product_name
were included in this example because they are required parameters for the API request, as opposed to L3 processing.The API response could look like this, with values being dependent on the values you sent in the request:
{
"create_time": 1583364540,
"reference_number": null,
"short_description": "Order from Mrs. Berkenshire for one elite mop on Oct. 31 1999.",
"long_description": null,
"type": "goods",
"delivery_type": null,
"buyer": null,
"status": null,
"customer_reference_number": "order-A78240981",
"tax_amount": 0,
"alternate_tax_amount": 0,
"alternate_tax_id": "EIN",
"vat_amount": 0,
"vat_rate": 0.0,
"discount_amount": 0,
"duty_amount": 0,
"freight_amount": 1000,
"grand_total_amount": 0,
"ship_to_address": {
"country": "US",
"postal_code": "90210"
},
"ship_from_address": {
"country": "US",
"postal_code": "94101"
},
"shipping_status": null,
"service_address": null,
"notes": null,
"terms": null,
"line_items": [
{
"item": {
"create_time": 1583364540,
"reference_number": null,
"photos": null,
"product_code": "E-M1",
"product_name": "Elite Basic Mop",
"description": null,
"shipping_options": null,
"product_details": null,
"price": 0,
"currency": "USD",
"terms": null,
"uri": null,
"weight": null,
"owner": {
"id": "{merchant's-account-id}",
"path": "/accounts/{merchant's-account-id}",
"resource": "accounts"
},
"id": "d44a32ce-b4c3-4cc1-a1e8-12876860c302",
"path": "/items/d44a32ce-b4c3-4cc1-a1e8-12876860c302",
"resource": "items",
"state": "active"
},
"currency": "USD",
"quantity": 2,
"tax_rate": 0,
"tax_amount": 0,
"unit_price": 2000,
"description": "Elite mop with green handle and white mop head.",
"total_amount": 2000,
"commodity_code": "25101803",
"discount_amount": 0,
"unit_of_measure": "ITM",
"tax_type_applied": "state sales tax",
"discount_indicator": "N",
"gross_net_indicator": "Y"
}
],
"currency": null,
"owner": {
"id": "{merchant's-account-id}",
"path": "/accounts/{merchnt's-account-id}",
"resource": "accounts"
},
"id": "0f0a0d95-2f2c-43eb-a9d1-d8e01aa40abf",
"path": "/orders/0f0a0d95-2f2c-43eb-a9d1-d8e01aa40abf",
"resource": "orders",
"state": "active",
"api_version": "3.0"
}
id
s in association with the Order id
. Next, you'll use the Oder's id
to attach the L2/L3 data to the transaction itself.Create an L2 or L3 Payment
Once you have an Order ID with the final transaction information, send aPOST /payments
request with that Order ID, like so:curl -X POST \
--url 'https://stage-api.wepay.com/payments' \
-H 'Accept: application/json'\
-H 'App-Id: {your-app-id}'\
-H 'App-Token: {your-app-token}'\
-H 'Api-Version: 3.0'\
-H 'Content-Type: application/json'\
-H 'Unique-Key: Unique-Key0' \
--data-raw '{
"account_id": "{merchant's-account-id}",
"amount": "5000",
"fee_amount": 800,
"auto_capture": true,
"currency": "USD",
"fee_amount": "{app's-fees}",
"order_id": "0f0a0d95-2f2c-43eb-a9d1-d8e01aa40abf",
"payment_method": {
"type": "payment_method_id",
"payment_method_id": "{previously-tokenized-payment-method}"
},
"initiated_by": "customer"
}'
Once the Payment has L2/L3 data, WePay and the card networks will determine the transaction's eligibility for L2/L3 pricing. Note that card networks will ultimately have the final say in a transaction's L2/L3 eligibility.
Partners using revenue share and/or IC+ with us will see processing cost reductions in their WePay account.
Create Inline Orders on Payments
Use thePOST/ payments
when you are creating a payment. Note, you cannot have both an order_id
and order
in the same POST/ payments
request, doing this will return a 400 error.curl -X POST \
--url 'https://stage-api.wepay.com/payments' \
-H 'Accept: application/json'\
-H 'App-Id: {your-app-id}'\
-H 'App-Token: {your-app-token}'\
-H 'Api-Version: 3.0'\
-H 'Content-Type: application/json'\
-H 'Unique-Key: Unique-Key' \
--data-raw '{
"account_id": "{merchant's-account-id}",
"amount": "5000",
"fee_amount": 800,
"auto_capture": true,
"currency": "USD",
"fee_amount": "{app's-fees}",
"order": {
"type": "goods",
"short_description": "short_description6"
},
"payment_method": {
"type": "payment_method_id",
"payment_method_id": "{previously-tokenized-payment-method}"
},
"initiated_by": "customer"
}'
order_id
to use for resource lookup later on the GET/ Orders
endpoint.Capture Payments Later
If a transaction is being completed with Manual or Partial capture, your process for sending L2/L3 data will go as follows:- Create the API Order with information known
- Create the API Payment with the Order
id
- If any information in the Order changes, call
POST /orders/{id}
; if any information in the Item changes, callPOST /items/{id}
- Once the final capture information for the Payment is known, verify that the related Order and Items reflect accurate information
- Send the
POST /payments/{id}/capture
request
Note
capture_at
time.POST /payments
request will be the same as above, with the exception of the auto_capture
parameter.For the sake of example, let's assume that the final transaction amount has changed due to a decrease in the shipping cost and a request from the purchaser reduce the item quantity from 2 to 1 due to an accidental duplicate purchase.
As mentioned in above sections, you should have the data from the initialPOST /orders
request. Work from that data, and apply updates where needed. Following the example, you'll need to update the freight_amount
and line_items.item.quantity
parameters like so:curl -X POST \
--url 'https://stage-api.wepay.com/orders/0f0a0d95-2f2c-43eb-a9d1-d8e01aa40abf' \
-H 'Accept: application/json'\
-H 'App-Id: {your-app-id}'\
-H 'App-Token: {your-app-token}'\
-H 'Api-Version: 3.0'\
-H 'Content-Type: application/json' \
--data-raw '{
"account_id": "{merchant's-account-id}",
"short_description": "Order from Mrs. Berkenshire for one elite mop on Oct. 31 1999.",
"type": "goods",
"customer_reference_number": "order-A78240981",
"tax_amount": 0,
"discount_amount": 0,
"duty_amount": 0,
"freight_amount": 750,
"alternate_tax_id": "EIN",
"alternate_tax_amount": 0,
"ship_to_address": {
"country": "US",
"postal_code": "90210"
},
"ship_from_address": {
"country": "US",
"postal_code": "94101"
},
"line_items": [
{
"currency": "USD",
"quantity": 1,
"commodity_code": "25101803",
"discount_amount": 0,
"description": "Elite mop with green handle and white mop head.",
"tax_amount": 0,
"tax_rate": 0,
"total_amount": 2000,
"unit_of_measure": "ITM",
"gross_net_indicator": "Y",
"discount_indicator": "N",
"tax_type_applied": "state sales tax",
"unit_price": 2000,
"item": {
"account_id": "{merchant's-account-id}",
"product_code": "E-M1",
"product_name": "Elite Basic Mop"
}
}
]
}'
Note that a new Item will be created, and the new Item ID will be the one associated with the Order. For best practice, delete the old Item like so:
curl -X DELETE \
--url 'https://stage-api.wepay.com/items/d44a32ce-b4c3-4cc1-a1e8-12876860c302' \
-H 'Accept: application/json'\
-H 'App-Id: {your-app-id}'\
-H 'App-Token: {your-app-token}'\
-H 'Api-Version: 3.0'
POST /payments/{id}/capture
request will look like this:curl -X POST \
--url 'https://stage-api.wepay.com/payments/{id}/capture' \
-H 'Accept: application/json'\
-H 'App-Id: {your-app-id}'\
-H 'App-Token: {your-app-token}'\
-H 'Api-Version: 3.0'\
-H 'Content-Type: application/json' \
--data-raw '{
"amounts": {
"amount": 2750,
"currency": "USD",
"fee_amount": 400
}
}'
amounts.amount
value is changing from the original value, and that fee_amount
cannot exceed 20% of the total amount.Once the Payment has L2/L3 data and is captured, WePay and the card networks will determine the transaction's eligibility for L2/L3 pricing. Note that card networks will ultimately have the final say in a transaction's L2/L3 eligibility.
Partners using revenue share and/or IC+ with us will see processing cost reductions in their WePay account.