> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.mercoa.com/virtual-card-agent/integrating-with-your-billpay/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.mercoa.com/_mcp/server. # Integrating Mercoa with your BillPay / AP System ## Overview Mercoa's Virtual Card Agent seamlessly integrates into your existing BillPay / AP workflows to automatically process invoice payments using virtual cards. It's designed to automatically pay invoices using virtual cards whenever it's possible and cost-effective. The integration includes a straightforward decision-based flow with a fallback to your current payment system, ensuring no disruption. ## Integration Flow This process is handled in two main phases: **validation** and **processing**. Your system first checks if an invoice is eligible for card payment and then acts on that information. ### Validation Flow This chart shows the initial decision-making process. ```mermaid graph LR A[**Payment
Scheduled**] --> B[Validate with
Mercoa Agent] B --> C{Card
Accepted?} C -->|No| D[Use Current
Workflow] C -->|Yes| E[Check
Processing Fee] E --> F{Fee
Acceptable?} F -->|No| D F -->|Yes| G[Proceed to
Processing] style A fill:#e1f5fe,color:#01579b,stroke:#b3e5fc style D fill:#fff3e0,color:#e65100,stroke:#ffe0b2 style G fill:#e8f5e9,color:#1b5e20,stroke:#c8e6c9 ``` ### Processing Flow If an invoice is cleared for card payment, this is the next step. ```mermaid graph LR A[Start Processing] --> B[Process with
Virtual Card] B --> C{Payment
Success?} C -->|No| D[Use Current
Workflow] C -->|Yes| E[Reconcile
Payment] E --> F[Mark Invoice
as Paid] style A fill:#e1f5fe,color:#01579b,stroke:#b3e5fc style D fill:#fff3e0,color:#e65100,stroke:#ffe0b2 style F fill:#e8f5e9,color:#1b5e20,stroke:#c8e6c9 ``` ## Implementation Steps ### 1. Trigger the Validation When a payment is scheduled in your system, make a call to the Mercoa Agent's validation endpoint. This is the entry point to the workflow. ```javascript // Example: When payment is scheduled async function schedulePayment(invoice) { // Your existing payment scheduling logic const scheduledPayment = await createScheduledPayment(invoice); // Call Mercoa Agent validation const validationResult = await validateWithMercoaAgent(invoice); // Store validation result for later use await storeValidationResult(scheduledPayment.id, validationResult); return scheduledPayment; } ``` ### 2. Call the Validation Endpoint Use the Mercoa Agent validation endpoint to check if the vendor's invoice can be processed with a virtual card. \" \ -H "Content-Type: application/json" \ -d '{ "type": "document", "document": "data:application/pdf;base64,JVBERi0xLjEKJcKlwrHDqwoKMSAwIG9iagogIDw8IC9UeXBlIC9DYXRhbG9nCiAgICAgL1BhZ2VzIDIgMCBSCiAgPj4KZW5kb2JqCgoyIDAgb2JqCiAgPDwgL1R5cGUgL1BhZ2VzCiAgICAgL0tpZHMgWzMgMCBSXQogICAgIC9Db3VudCAxCiAgICAgL01lZGlhQm94IFswIDAgMzAwIDE0NF0KICA+PgplbmRvYmoKCjMgMCBvYmoKICA8PCAgL1R5cGUgL1BhZ2UKICAgICAgL1BhcmVudCAyIDAgUgogICAgICAvUmVzb3VyY2VzCiAgICAgICA8PCAvRm9udAogICAgICAgICAgIDw8IC9GMQogICAgICAgICAgICAgICA8PCAvVHlwZSAvRm9udAogICAgICAgICAgICAgICAgICAvU3VidHlwZSAvVHlwZTEKICAgICAgICAgICAgICAgICAgL0Jhc2VGb250IC9UaW1lcy1Sb21hbgogICAgICAgICAgICAgICA+PgogICAgICAgICAgID4+CiAgICAgICA+PgogICAgICAvQ29udGVudHMgNCAwIFIKICA+PgplbmRvYmoKCjQgMCBvYmoKICA8PCAvTGVuZ3RoIDU1ID4+CnN0cmVhbQogIEJUCiAgICAvRjEgMTggVGYKICAgIDAgMCBUZAogICAgKEhlbGxvIFdvcmxkKSBUagogIEVUCmVuZHN0cmVhbQplbmRvYmoKCnhyZWYKMCA1CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxOCAwMDAwMCBuIAowMDAwMDAwMDc3IDAwMDAwIG4gCjAwMDAwMDAxNzggMDAwMDAgbiAKMDAwMDAwMDQ1NyAwMDAwMCBuIAp0cmFpbGVyCiAgPDwgIC9Sb290IDEgMCBSCiAgICAgIC9TaXplIDUKICA+PgpzdGFydHhyZWYKNTY1CiUlRU9GCg==" }' ``` **`Document`** ```python Document import requests url = "https://api.mercoa.com/payment-gateway/validate" payload = { "type": "document", "document": "data:application/pdf;base64,JVBERi0xLjEKJcKlwrHDqwoKMSAwIG9iagogIDw8IC9UeXBlIC9DYXRhbG9nCiAgICAgL1BhZ2VzIDIgMCBSCiAgPj4KZW5kb2JqCgoyIDAgb2JqCiAgPDwgL1R5cGUgL1BhZ2VzCiAgICAgL0tpZHMgWzMgMCBSXQogICAgIC9Db3VudCAxCiAgICAgL01lZGlhQm94IFswIDAgMzAwIDE0NF0KICA+PgplbmRvYmoKCjMgMCBvYmoKICA8PCAgL1R5cGUgL1BhZ2UKICAgICAgL1BhcmVudCAyIDAgUgogICAgICAvUmVzb3VyY2VzCiAgICAgICA8PCAvRm9udAogICAgICAgICAgIDw8IC9GMQogICAgICAgICAgICAgICA8PCAvVHlwZSAvRm9udAogICAgICAgICAgICAgICAgICAvU3VidHlwZSAvVHlwZTEKICAgICAgICAgICAgICAgICAgL0Jhc2VGb250IC9UaW1lcy1Sb21hbgogICAgICAgICAgICAgICA+PgogICAgICAgICAgID4+CiAgICAgICA+PgogICAgICAvQ29udGVudHMgNCAwIFIKICA+PgplbmRvYmoKCjQgMCBvYmoKICA8PCAvTGVuZ3RoIDU1ID4+CnN0cmVhbQogIEJUCiAgICAvRjEgMTggVGYKICAgIDAgMCBUZAogICAgKEhlbGxvIFdvcmxkKSBUagogIEVUCmVuZHN0cmVhbQplbmRvYmoKCnhyZWYKMCA1CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxOCAwMDAwMCBuIAowMDAwMDAwMDc3IDAwMDAwIG4gCjAwMDAwMDAxNzggMDAwMDAgbiAKMDAwMDAwMDQ1NyAwMDAwMCBuIAp0cmFpbGVyCiAgPDwgIC9Sb290IDEgMCBSCiAgICAgIC9TaXplIDUKICA+PgpzdGFydHhyZWYKNTY1CiUlRU9GCg==" } headers = { "Authorization": "Bearer ", "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers) print(response.json()) ``` **`Document`** ```typescript Document import { MercoaClient } from "@mercoa/javascript"; const client = new MercoaClient({ token: "YOUR_TOKEN" }); await client.paymentGateway.validate.create({ type: "document", document: "data:application/pdf;base64,JVBERi0xLjEKJcKlwrHDqwoKMSAwIG9iagogIDw8IC9UeXBlIC9DYXRhbG9nCiAgICAgL1BhZ2VzIDIgMCBSCiAgPj4KZW5kb2JqCgoyIDAgb2JqCiAgPDwgL1R5cGUgL1BhZ2VzCiAgICAgL0tpZHMgWzMgMCBSXQogICAgIC9Db3VudCAxCiAgICAgL01lZGlhQm94IFswIDAgMzAwIDE0NF0KICA+PgplbmRvYmoKCjMgMCBvYmoKICA8PCAgL1R5cGUgL1BhZ2UKICAgICAgL1BhcmVudCAyIDAgUgogICAgICAvUmVzb3VyY2VzCiAgICAgICA8PCAvRm9udAogICAgICAgICAgIDw8IC9GMQogICAgICAgICAgICAgICA8PCAvVHlwZSAvRm9udAogICAgICAgICAgICAgICAgICAvU3VidHlwZSAvVHlwZTEKICAgICAgICAgICAgICAgICAgL0Jhc2VGb250IC9UaW1lcy1Sb21hbgogICAgICAgICAgICAgICA+PgogICAgICAgICAgID4+CiAgICAgICA+PgogICAgICAvQ29udGVudHMgNCAwIFIKICA+PgplbmRvYmoKCjQgMCBvYmoKICA8PCAvTGVuZ3RoIDU1ID4+CnN0cmVhbQogIEJUCiAgICAvRjEgMTggVGYKICAgIDAgMCBUZAogICAgKEhlbGxvIFdvcmxkKSBUagogIEVUCmVuZHN0cmVhbQplbmRvYmoKCnhyZWYKMCA1CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxOCAwMDAwMCBuIAowMDAwMDAwMDc3IDAwMDAwIG4gCjAwMDAwMDAxNzggMDAwMDAgbiAKMDAwMDAwMDQ1NyAwMDAwMCBuIAp0cmFpbGVyCiAgPDwgIC9Sb290IDEgMCBSCiAgICAgIC9TaXplIDUKICA+PgpzdGFydHhyZWYKNTY1CiUlRU9GCg==" }); ``` **`Document`** ```go Document package main import ( "fmt" "strings" "net/http" "io" ) func main() { url := "https://api.mercoa.com/payment-gateway/validate" payload := strings.NewReader("{\n \"type\": \"document\",\n \"document\": \"data:application/pdf;base64,JVBERi0xLjEKJcKlwrHDqwoKMSAwIG9iagogIDw8IC9UeXBlIC9DYXRhbG9nCiAgICAgL1BhZ2VzIDIgMCBSCiAgPj4KZW5kb2JqCgoyIDAgb2JqCiAgPDwgL1R5cGUgL1BhZ2VzCiAgICAgL0tpZHMgWzMgMCBSXQogICAgIC9Db3VudCAxCiAgICAgL01lZGlhQm94IFswIDAgMzAwIDE0NF0KICA+PgplbmRvYmoKCjMgMCBvYmoKICA8PCAgL1R5cGUgL1BhZ2UKICAgICAgL1BhcmVudCAyIDAgUgogICAgICAvUmVzb3VyY2VzCiAgICAgICA8PCAvRm9udAogICAgICAgICAgIDw8IC9GMQogICAgICAgICAgICAgICA8PCAvVHlwZSAvRm9udAogICAgICAgICAgICAgICAgICAvU3VidHlwZSAvVHlwZTEKICAgICAgICAgICAgICAgICAgL0Jhc2VGb250IC9UaW1lcy1Sb21hbgogICAgICAgICAgICAgICA+PgogICAgICAgICAgID4+CiAgICAgICA+PgogICAgICAvQ29udGVudHMgNCAwIFIKICA+PgplbmRvYmoKCjQgMCBvYmoKICA8PCAvTGVuZ3RoIDU1ID4+CnN0cmVhbQogIEJUCiAgICAvRjEgMTggVGYKICAgIDAgMCBUZAogICAgKEhlbGxvIFdvcmxkKSBUagogIEVUCmVuZHN0cmVhbQplbmRvYmoKCnhyZWYKMCA1CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxOCAwMDAwMCBuIAowMDAwMDAwMDc3IDAwMDAwIG4gCjAwMDAwMDAxNzggMDAwMDAgbiAKMDAwMDAwMDQ1NyAwMDAwMCBuIAp0cmFpbGVyCiAgPDwgIC9Sb290IDEgMCBSCiAgICAgIC9TaXplIDUKICA+PgpzdGFydHhyZWYKNTY1CiUlRU9GCg==\"\n}") req, _ := http.NewRequest("POST", url, payload) req.Header.Add("Authorization", "Bearer ") req.Header.Add("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := io.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body)) } ``` **`Document`** ```ruby Document require 'uri' require 'net/http' url = URI("https://api.mercoa.com/payment-gateway/validate") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true request = Net::HTTP::Post.new(url) request["Authorization"] = 'Bearer ' request["Content-Type"] = 'application/json' request.body = "{\n \"type\": \"document\",\n \"document\": \"data:application/pdf;base64,JVBERi0xLjEKJcKlwrHDqwoKMSAwIG9iagogIDw8IC9UeXBlIC9DYXRhbG9nCiAgICAgL1BhZ2VzIDIgMCBSCiAgPj4KZW5kb2JqCgoyIDAgb2JqCiAgPDwgL1R5cGUgL1BhZ2VzCiAgICAgL0tpZHMgWzMgMCBSXQogICAgIC9Db3VudCAxCiAgICAgL01lZGlhQm94IFswIDAgMzAwIDE0NF0KICA+PgplbmRvYmoKCjMgMCBvYmoKICA8PCAgL1R5cGUgL1BhZ2UKICAgICAgL1BhcmVudCAyIDAgUgogICAgICAvUmVzb3VyY2VzCiAgICAgICA8PCAvRm9udAogICAgICAgICAgIDw8IC9GMQogICAgICAgICAgICAgICA8PCAvVHlwZSAvRm9udAogICAgICAgICAgICAgICAgICAvU3VidHlwZSAvVHlwZTEKICAgICAgICAgICAgICAgICAgL0Jhc2VGb250IC9UaW1lcy1Sb21hbgogICAgICAgICAgICAgICA+PgogICAgICAgICAgID4+CiAgICAgICA+PgogICAgICAvQ29udGVudHMgNCAwIFIKICA+PgplbmRvYmoKCjQgMCBvYmoKICA8PCAvTGVuZ3RoIDU1ID4+CnN0cmVhbQogIEJUCiAgICAvRjEgMTggVGYKICAgIDAgMCBUZAogICAgKEhlbGxvIFdvcmxkKSBUagogIEVUCmVuZHN0cmVhbQplbmRvYmoKCnhyZWYKMCA1CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxOCAwMDAwMCBuIAowMDAwMDAwMDc3IDAwMDAwIG4gCjAwMDAwMDAxNzggMDAwMDAgbiAKMDAwMDAwMDQ1NyAwMDAwMCBuIAp0cmFpbGVyCiAgPDwgIC9Sb290IDEgMCBSCiAgICAgIC9TaXplIDUKICA+PgpzdGFydHhyZWYKNTY1CiUlRU9GCg==\"\n}" response = http.request(request) puts response.read_body ``` **`Document`** ```java Document import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.Unirest; HttpResponse response = Unirest.post("https://api.mercoa.com/payment-gateway/validate") .header("Authorization", "Bearer ") .header("Content-Type", "application/json") .body("{\n \"type\": \"document\",\n \"document\": \"data:application/pdf;base64,JVBERi0xLjEKJcKlwrHDqwoKMSAwIG9iagogIDw8IC9UeXBlIC9DYXRhbG9nCiAgICAgL1BhZ2VzIDIgMCBSCiAgPj4KZW5kb2JqCgoyIDAgb2JqCiAgPDwgL1R5cGUgL1BhZ2VzCiAgICAgL0tpZHMgWzMgMCBSXQogICAgIC9Db3VudCAxCiAgICAgL01lZGlhQm94IFswIDAgMzAwIDE0NF0KICA+PgplbmRvYmoKCjMgMCBvYmoKICA8PCAgL1R5cGUgL1BhZ2UKICAgICAgL1BhcmVudCAyIDAgUgogICAgICAvUmVzb3VyY2VzCiAgICAgICA8PCAvRm9udAogICAgICAgICAgIDw8IC9GMQogICAgICAgICAgICAgICA8PCAvVHlwZSAvRm9udAogICAgICAgICAgICAgICAgICAvU3VidHlwZSAvVHlwZTEKICAgICAgICAgICAgICAgICAgL0Jhc2VGb250IC9UaW1lcy1Sb21hbgogICAgICAgICAgICAgICA+PgogICAgICAgICAgID4+CiAgICAgICA+PgogICAgICAvQ29udGVudHMgNCAwIFIKICA+PgplbmRvYmoKCjQgMCBvYmoKICA8PCAvTGVuZ3RoIDU1ID4+CnN0cmVhbQogIEJUCiAgICAvRjEgMTggVGYKICAgIDAgMCBUZAogICAgKEhlbGxvIFdvcmxkKSBUagogIEVUCmVuZHN0cmVhbQplbmRvYmoKCnhyZWYKMCA1CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxOCAwMDAwMCBuIAowMDAwMDAwMDc3IDAwMDAwIG4gCjAwMDAwMDAxNzggMDAwMDAgbiAKMDAwMDAwMDQ1NyAwMDAwMCBuIAp0cmFpbGVyCiAgPDwgIC9Sb290IDEgMCBSCiAgICAgIC9TaXplIDUKICA+PgpzdGFydHhyZWYKNTY1CiUlRU9GCg==\"\n}") .asString(); ``` **`Document`** ```php Document request('POST', 'https://api.mercoa.com/payment-gateway/validate', [ 'body' => '{ "type": "document", "document": "data:application/pdf;base64,JVBERi0xLjEKJcKlwrHDqwoKMSAwIG9iagogIDw8IC9UeXBlIC9DYXRhbG9nCiAgICAgL1BhZ2VzIDIgMCBSCiAgPj4KZW5kb2JqCgoyIDAgb2JqCiAgPDwgL1R5cGUgL1BhZ2VzCiAgICAgL0tpZHMgWzMgMCBSXQogICAgIC9Db3VudCAxCiAgICAgL01lZGlhQm94IFswIDAgMzAwIDE0NF0KICA+PgplbmRvYmoKCjMgMCBvYmoKICA8PCAgL1R5cGUgL1BhZ2UKICAgICAgL1BhcmVudCAyIDAgUgogICAgICAvUmVzb3VyY2VzCiAgICAgICA8PCAvRm9udAogICAgICAgICAgIDw8IC9GMQogICAgICAgICAgICAgICA8PCAvVHlwZSAvRm9udAogICAgICAgICAgICAgICAgICAvU3VidHlwZSAvVHlwZTEKICAgICAgICAgICAgICAgICAgL0Jhc2VGb250IC9UaW1lcy1Sb21hbgogICAgICAgICAgICAgICA+PgogICAgICAgICAgID4+CiAgICAgICA+PgogICAgICAvQ29udGVudHMgNCAwIFIKICA+PgplbmRvYmoKCjQgMCBvYmoKICA8PCAvTGVuZ3RoIDU1ID4+CnN0cmVhbQogIEJUCiAgICAvRjEgMTggVGYKICAgIDAgMCBUZAogICAgKEhlbGxvIFdvcmxkKSBUagogIEVUCmVuZHN0cmVhbQplbmRvYmoKCnhyZWYKMCA1CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxOCAwMDAwMCBuIAowMDAwMDAwMDc3IDAwMDAwIG4gCjAwMDAwMDAxNzggMDAwMDAgbiAKMDAwMDAwMDQ1NyAwMDAwMCBuIAp0cmFpbGVyCiAgPDwgIC9Sb290IDEgMCBSCiAgICAgIC9TaXplIDUKICA+PgpzdGFydHhyZWYKNTY1CiUlRU9GCg==" }', 'headers' => [ 'Authorization' => 'Bearer ', 'Content-Type' => 'application/json', ], ]); echo $response->getBody(); ``` **`Document`** ```csharp Document using RestSharp; var client = new RestClient("https://api.mercoa.com/payment-gateway/validate"); var request = new RestRequest(Method.POST); request.AddHeader("Authorization", "Bearer "); request.AddHeader("Content-Type", "application/json"); request.AddParameter("application/json", "{\n \"type\": \"document\",\n \"document\": \"data:application/pdf;base64,JVBERi0xLjEKJcKlwrHDqwoKMSAwIG9iagogIDw8IC9UeXBlIC9DYXRhbG9nCiAgICAgL1BhZ2VzIDIgMCBSCiAgPj4KZW5kb2JqCgoyIDAgb2JqCiAgPDwgL1R5cGUgL1BhZ2VzCiAgICAgL0tpZHMgWzMgMCBSXQogICAgIC9Db3VudCAxCiAgICAgL01lZGlhQm94IFswIDAgMzAwIDE0NF0KICA+PgplbmRvYmoKCjMgMCBvYmoKICA8PCAgL1R5cGUgL1BhZ2UKICAgICAgL1BhcmVudCAyIDAgUgogICAgICAvUmVzb3VyY2VzCiAgICAgICA8PCAvRm9udAogICAgICAgICAgIDw8IC9GMQogICAgICAgICAgICAgICA8PCAvVHlwZSAvRm9udAogICAgICAgICAgICAgICAgICAvU3VidHlwZSAvVHlwZTEKICAgICAgICAgICAgICAgICAgL0Jhc2VGb250IC9UaW1lcy1Sb21hbgogICAgICAgICAgICAgICA+PgogICAgICAgICAgID4+CiAgICAgICA+PgogICAgICAvQ29udGVudHMgNCAwIFIKICA+PgplbmRvYmoKCjQgMCBvYmoKICA8PCAvTGVuZ3RoIDU1ID4+CnN0cmVhbQogIEJUCiAgICAvRjEgMTggVGYKICAgIDAgMCBUZAogICAgKEhlbGxvIFdvcmxkKSBUagogIEVUCmVuZHN0cmVhbQplbmRvYmoKCnhyZWYKMCA1CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxOCAwMDAwMCBuIAowMDAwMDAwMDc3IDAwMDAwIG4gCjAwMDAwMDAxNzggMDAwMDAgbiAKMDAwMDAwMDQ1NyAwMDAwMCBuIAp0cmFpbGVyCiAgPDwgIC9Sb290IDEgMCBSCiAgICAgIC9TaXplIDUKICA+PgpzdGFydHhyZWYKNTY1CiUlRU9GCg==\"\n}", ParameterType.RequestBody); IRestResponse response = client.Execute(request); ``` **`Document`** ````swift Document import Foundation let headers = [ "Authorization": "Bearer ", "Content-Type": "application/json" ] let parameters = [ "type": "document", "document": "data:application/pdf;base64,JVBERi0xLjEKJcKlwrHDqwoKMSAwIG9iagogIDw8IC9UeXBlIC9DYXRhbG9nCiAgICAgL1BhZ2VzIDIgMCBSCiAgPj4KZW5kb2JqCgoyIDAgb2JqCiAgPDwgL1R5cGUgL1BhZ2VzCiAgICAgL0tpZHMgWzMgMCBSXQogICAgIC9Db3VudCAxCiAgICAgL01lZGlhQm94IFswIDAgMzAwIDE0NF0KICA+PgplbmRvYmoKCjMgMCBvYmoKICA8PCAgL1R5cGUgL1BhZ2UKICAgICAgL1BhcmVudCAyIDAgUgogICAgICAvUmVzb3VyY2VzCiAgICAgICA8PCAvRm9udAogICAgICAgICAgIDw8IC9GMQogICAgICAgICAgICAgICA8PCAvVHlwZSAvRm9udAogICAgICAgICAgICAgICAgICAvU3VidHlwZSAvVHlwZTEKICAgICAgICAgICAgICAgICAgL0Jhc2VGb250IC9UaW1lcy1Sb21hbgogICAgICAgICAgICAgICA+PgogICAgICAgICAgID4+CiAgICAgICA+PgogICAgICAvQ29udGVudHMgNCAwIFIKICA+PgplbmRvYmoKCjQgMCBvYmoKICA8PCAvTGVuZ3RoIDU1ID4+CnN0cmVhbQogIEJUCiAgICAvRjEgMTggVGYKICAgIDAgMCBUZAogICAgKEhlbGxvIFdvcmxkKSBUagogIEVUCmVuZHN0cmVhbQplbmRvYmoKCnhyZWYKMCA1CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxOCAwMDAwMCBuIAowMDAwMDAwMDc3IDAwMDAwIG4gCjAwMDAwMDAxNzggMDAwMDAgbiAKMDAwMDAwMDQ1NyAwMDAwMCBuIAp0cmFpbGVyCiAgPDwgIC9Sb290IDEgMCBSCiAgICAgIC9TaXplIDUKICA+PgpzdGFydHhyZWYKNTY1CiUlRU9GCg==" ] as [String : Any] let postData = JSONSerialization.data(withJSONObject: parameters, options: []) let request = NSMutableURLRequest(url: NSURL(string: "https://api.mercoa.com/payment-gateway/validate")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "POST" request.allHTTPHeaderFields = headers request.httpBody = postData as Data let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: \{ (data, response, error) -> Void in if (error != nil) { print(error as Any) } else { let httpResponse = response as? HTTPURLResponse print(httpResponse) } }) dataTask.resume() ```Tab title="JavaScript"> ```javascript import { MercoaClient } from "@mercoa/javascript" const mercoa = new MercoaClient({ token: "YOUR_API_KEY", }) async function validateWithMercoaAgent(invoice) { const validationJob = await mercoa.paymentGateway.createValidationJob({ type: "html", html: `

Invoice ${invoice.id}

Amount: $${invoice.amount}

` }) return validationJob } ```` \ #### Python ```python from mercoa.client import Mercoa mercoa = Mercoa(token="YOUR_API_KEY") async def validate_with_mercoa_agent(invoice): validation_job = await mercoa.payment_gateway.create_validation_job({ "type": "html", "html": f"

Invoice {invoice['id']}

Amount: ${invoice['amount']}

" }) return validation_job ``` #### Go ```go import ( mercoa "github.com/mercoa-finance/go" mercoaclient "github.com/mercoa-finance/go/client" "github.com/mercoa-finance/go/option" ) client := mercoaclient.NewClient( option.WithToken("YOUR_API_KEY"), ) func validateWithMercoaAgent(invoice map[string]interface{}) (*mercoa.CreateValidationJobResponse, error) { response, err := client.PaymentGateway.CreateValidationJob( context.TODO(), &mercoa.CreateValidationJobRequest{ Type: "html", Html: fmt.Sprintf("

Invoice %s

Amount: $%.2f

", invoice["id"], invoice["amount"]), }, ) return response, err } ``` #### Java ```java import com.mercoa.Mercoa; Mercoa client = Mercoa.builder() .token("YOUR_API_KEY") .build(); public CreateValidationJobResponse validateWithMercoaAgent(Map invoice) { CreateValidationJobRequest request = CreateValidationJobRequest.builder() .type("html") .html(String.format("

Invoice %s

Amount: $%.2f

", invoice.get("id"), invoice.get("amount"))) .build(); return client.paymentGateway().createValidationJob(request); } ``` \ ### 3. Analyze the Validation Response The validation endpoint returns a response indicating whether the invoice can be paid with a card and lists any applicable fees. * Whether the invoice can be processed with a virtual card * The processing fee (if applicable) * Any additional requirements or restrictions ```json { "canProcessWithCard": true, "processingFee": 2.50, "feeCurrency": "USD", "restrictions": [], "estimatedProcessingTime": "1-2 business days" } ``` ### 4. Apply Your Decision Logic Based on the validation response, implement logic on your side to decide the next step. * If card processing isn't available, fall back to your current workflow. * If it is available, check if the fee is within your acceptable threshold. If not, fall back. * If the fee is acceptable, proceed to process the payment with a virtual card. #### JavaScript ```javascript async function processPaymentDecision(invoice, validationResult) \{ // If card processing is not available, use current workflow if (validationResult.card?.eligibility !== 'ACCEPTED') { return await processWithCurrentWorkflow(invoice); } // Check if fee is acceptable (implement your threshold logic) const feeThreshold = getFeeThreshold(invoice.amount); const processingFee = validationResult.card?.fee?.value || 0; if (processingFee > feeThreshold) { return await processWithCurrentWorkflow(invoice); } // Proceed with virtual card processing return await processWithVirtualCard(invoice, validationResult); } ``` #### Python ```python async def process_payment_decision(invoice, validation_result): # If card processing is not available, use current workflow if validation_result.card.eligibility != 'ACCEPTED': return await process_with_current_workflow(invoice) # Check if fee is acceptable (implement your threshold logic) fee_threshold = get_fee_threshold(invoice['amount']) processing_fee = validation_result.card.fee.value if validation_result.card.fee else 0 if processing_fee > fee_threshold: return await process_with_current_workflow(invoice) # Proceed with virtual card processing return await process_with_virtual_card(invoice, validation_result) ``` #### Go ```go func processPaymentDecision(invoice map[string]interface{}, validationResult *mercoa.CreateValidationJobResponse) (*mercoa.CreateProcessJobResponse, error) \{ // If card processing is not available, use current workflow if validationResult.Card.Eligibility != "ACCEPTED" { return processWithCurrentWorkflow(invoice) } // Check if fee is acceptable (implement your threshold logic) feeThreshold := getFeeThreshold(invoice["amount"].(float64)) processingFee := 0.0 if validationResult.Card.Fee != nil { processingFee = validationResult.Card.Fee.Value } if processingFee > feeThreshold { return processWithCurrentWorkflow(invoice) } // Proceed with virtual card processing return processWithVirtualCard(invoice, validationResult) } ``` #### Java ```java public CreateProcessJobResponse processPaymentDecision(Map invoice, CreateValidationJobResponse validationResult) \{ // If card processing is not available, use current workflow if (!"ACCEPTED".equals(validationResult.getCard().getEligibility())) { return processWithCurrentWorkflow(invoice); } // Check if fee is acceptable (implement your threshold logic) double feeThreshold = getFeeThreshold((Double) invoice.get("amount")); double processingFee = 0.0; if (validationResult.getCard().getFee() != null) { processingFee = validationResult.getCard().getFee().getValue(); } if (processingFee > feeThreshold) { return processWithCurrentWorkflow(invoice); } // Proceed with virtual card processing return processWithVirtualCard(invoice, validationResult); } ``` ### 5. Process the Payment If the decision is to use a virtual card, call Mercoa process endpoint. Your implementation should handle both success and failure. If the payment fails for any reason, fall back to your existing workflow. #### JavaScript ```javascript async function processWithVirtualCard(invoice, validationResult) { try { const processJob = await mercoa.paymentGateway.createProcessJob({ type: "html", html: `

Invoice ${invoice.id}

Amount: $${invoice.amount}

`, cardDetails: { type: "stripeIssuing", firstName: "John", lastName: "Doe", postalCode: "12345", country: "US", stripeCardId: "ic_1234567890abcdef", stripePublishableKey: "pk_test_1234567890abcdef", ephemeralKeyEndpoint: { url: "https://api.example.com/ephemeral-keys", method: "POST", headers: { "Authorization": "Bearer YOUR_AUTH_SCHEME", "Content-Type": "application/json" }, postBody: "{\"card_id\": \"{{cardId}}\", \"nonce\": \"{{nonce}}\", \"account_id\": \"{{accountId}}\"}" } } }) if (processJob.jobStatus === 'success') { // Reconcile the payment with your invoice await reconcilePayment(invoice.id, processJob.receiptUrl); return { success: true, method: 'virtual_card' }; } else { // Fall back to current workflow return await processWithCurrentWorkflow(invoice); } } catch (error) { // Handle errors and fall back to current workflow console.error('Virtual card processing failed:', error); return await processWithCurrentWorkflow(invoice); } } ``` #### Python ```python async def process_with_virtual_card(invoice, validation_result): try: process_job = await mercoa.payment_gateway.create_process_job({ "type": "html", "html": f"

Invoice {invoice['id']}

Amount: ${invoice['amount']}

", "cardDetails": { "type": "stripeIssuing", "firstName": "John", "lastName": "Doe", "postalCode": "12345", "country": "US", "stripeCardId": "ic_1234567890abcdef", "stripePublishableKey": "pk_test_1234567890abcdef", "ephemeralKeyEndpoint": { "url": "https://api.example.com/ephemeral-keys", "method": "POST", "headers": { "Authorization": "Bearer YOUR_AUTH_SCHEME", "Content-Type": "application/json" }, "postBody": "{\"card_id\": \"{{cardId}}\", \"nonce\": \"{{nonce}}\", \"account_id\": \"{{accountId}}\"}" } } }) if process_job.job_status == 'success': # Reconcile the payment with your invoice await reconcile_payment(invoice['id'], process_job.receipt_url) return {"success": True, "method": "virtual_card"} else: # Fall back to current workflow return await process_with_current_workflow(invoice) except Exception as error: # Handle errors and fall back to current workflow print(f"Virtual card processing failed: {error}") return await process_with_current_workflow(invoice) ``` #### Go ```go func processWithVirtualCard(invoice map[string]interface{}, validationResult *mercoa.CreateValidationJobResponse) (*mercoa.CreateProcessJobResponse, error) { response, err := client.PaymentGateway.CreateProcessJob( context.TODO(), &mercoa.CreateProcessJobRequest{ Type: "html", Html: fmt.Sprintf("

Invoice %s

Amount: $%.2f

", invoice["id"], invoice["amount"]), CardDetails: &mercoa.CardDetails{ Type: "stripeIssuing", FirstName: "John", LastName: "Doe", PostalCode: "12345", Country: "US", StripeCardId: "ic_1234567890abcdef", StripePublishableKey: "pk_test_1234567890abcdef", EphemeralKeyEndpoint: &mercoa.EphemeralKeyEndpoint{ Url: "https://api.example.com/ephemeral-keys", Method: "POST", Headers: map[string]string{ "Authorization": "Bearer YOUR_AUTH_SCHEME", "Content-Type": "application/json", }, PostBody: "{\"card_id\": \"{{cardId}}\", \"nonce\": \"{{nonce}}\", \"account_id\": \"{{accountId}}\"}", }, }, }, ) if err != nil { return nil, err } if response.JobStatus == "success" { // Reconcile the payment with your invoice reconcilePayment(invoice["id"].(string), response.ReceiptUrl) return response, nil } else { // Fall back to current workflow return processWithCurrentWorkflow(invoice) } } ``` #### Java ```java public CreateProcessJobResponse processWithVirtualCard(Map invoice, CreateValidationJobResponse validationResult) { try { CreateProcessJobRequest request = CreateProcessJobRequest.builder() .type("html") .html(String.format("

Invoice %s

Amount: $%.2f

", invoice.get("id"), invoice.get("amount"))) .cardDetails(CardDetails.builder() .type("stripeIssuing") .firstName("John") .lastName("Doe") .postalCode("12345") .country("US") .stripeCardId("ic_1234567890abcdef") .stripePublishableKey("pk_test_1234567890abcdef") .ephemeralKeyEndpoint(EphemeralKeyEndpoint.builder() .url("https://api.example.com/ephemeral-keys") .method("POST") .headers(Map.of( "Authorization", "Bearer YOUR_AUTH_SCHEME", "Content-Type", "application/json" )) .postBody("{\"card_id\": \"{{cardId}}\", \"nonce\": \"{{nonce}}\", \"account_id\": \"{{accountId}}\"}") .build()) .build()) .build(); CreateProcessJobResponse processJob = client.paymentGateway().createProcessJob(request); if ("success".equals(processJob.getJobStatus())) { // Reconcile the payment with your invoice reconcilePayment((String) invoice.get("id"), processJob.getReceiptUrl()); return processJob; } else { // Fall back to current workflow return processWithCurrentWorkflow(invoice); } } catch (Exception error) { // Handle errors and fall back to current workflow System.err.println("Virtual card processing failed: " + error.getMessage()); return processWithCurrentWorkflow(invoice); } } ``` ### 6. Reconcile the Payment When a virtual card payment succeeds, you must reconcile the payment on your side. This typically means updating your invoice's status to "paid" and storing the transaction details for your records. #### JavaScript ```javascript async function reconcilePayment(invoiceId, receiptUrl) { // Update your invoice status to paid await updateInvoiceStatus(invoiceId, 'paid'); // Store transaction details for audit trail await storeTransactionDetails(invoiceId, receiptUrl, { method: 'virtual_card', processedAt: new Date(), // Include other relevant transaction data }); // Trigger any post-payment workflows await triggerPostPaymentWorkflows(invoiceId); } ``` #### Python ```python async def reconcile_payment(invoice_id, receipt_url): # Update your invoice status to paid await update_invoice_status(invoice_id, 'paid') # Store transaction details for audit trail await store_transaction_details(invoice_id, receipt_url, { 'method': 'virtual_card', 'processed_at': datetime.now(), # Include other relevant transaction data }) # Trigger any post-payment workflows await trigger_post_payment_workflows(invoice_id) ``` #### Go ```go func reconcilePayment(invoiceId string, receiptUrl string) error { // Update your invoice status to paid err := updateInvoiceStatus(invoiceId, "paid") if err != nil { return err } // Store transaction details for audit trail err = storeTransactionDetails(invoiceId, receiptUrl, map[string]interface{}{ "method": "virtual_card", "processed_at": time.Now(), // Include other relevant transaction data }) if err != nil { return err } // Trigger any post-payment workflows return triggerPostPaymentWorkflows(invoiceId) } ``` #### Java ```java public void reconcilePayment(String invoiceId, String receiptUrl) { // Update your invoice status to paid updateInvoiceStatus(invoiceId, "paid"); // Store transaction details for audit trail Map transactionDetails = Map.of( "method", "virtual_card", "processed_at", LocalDateTime.now(), // Include other relevant transaction data ); storeTransactionDetails(invoiceId, receiptUrl, transactionDetails); // Trigger any post-payment workflows triggerPostPaymentWorkflows(invoiceId); } ``` ## Migration Strategy We recommend a phased approach to roll out the integration. ### Phase 1: Validation Only * Implement the validation endpoint call. * Log the results to analyze vendor eligibility rates and potential fees without actually processing payments. ### Phase 2: Limited Processing * Enable virtual card processing for a small, selected group of low-risk vendors or invoices. * Monitor success rates and ensure your reconciliation logic works as expected. ### Phase 3: Full Integration * Enable the workflow for all eligible invoices. * Ensure your fallback mechanisms are robust. * Continuously monitor and optimize based on performance data.