Repository: moov-io/customers
Pull request #47 (2019-10-30 20:32:30)

cmd/server: add disclaimers to OpenAPI spec, render accepted disclaimer

Issue: https://github.com/moov-io/paygate/issues/98


Patch:
diff --git a/client/README.md b/client/README.md
index c737723..3122c7a 100644
--- a/client/README.md
+++ b/client/README.md
@@ -33,9 +33,11 @@ All URIs are relative to *http://localhost:8087*
 
 Class | Method | HTTP request | Description
 ------------ | ------------- | ------------- | -------------
+*CustomersApi* | [**AcceptDisclaimer**](docs/CustomersApi.md#acceptdisclaimer) | **Post** /customers/{customerID}/disclaimers | Accept a disclaimer for the given customer
 *CustomersApi* | [**AddCustomerAddress**](docs/CustomersApi.md#addcustomeraddress) | **Post** /customers/{customerID}/address | Add an Address onto an existing Customer record
 *CustomersApi* | [**CreateCustomer**](docs/CustomersApi.md#createcustomer) | **Post** /customers | Create a new customer
 *CustomersApi* | [**GetCustomer**](docs/CustomersApi.md#getcustomer) | **Get** /customers/{customerID} | Retrieves a Customer object associated with the customer ID.
+*CustomersApi* | [**GetCustomerDisclaimers**](docs/CustomersApi.md#getcustomerdisclaimers) | **Get** /customers/{customerID}/disclaimers | Get active disclaimers for the given customer
 *CustomersApi* | [**GetCustomerDocumentContents**](docs/CustomersApi.md#getcustomerdocumentcontents) | **Get** /customers/{customerID}/documents/{documentID} | Retrieve the referenced document
 *CustomersApi* | [**GetCustomerDocuments**](docs/CustomersApi.md#getcustomerdocuments) | **Get** /customers/{customerID}/documents | Get documents for a customer
 *CustomersApi* | [**Ping**](docs/CustomersApi.md#ping) | **Get** /ping | Ping the Customers service to check if running
diff --git a/client/api/openapi.yaml b/client/api/openapi.yaml
index e9552f5..927612c 100644
--- a/client/api/openapi.yaml
+++ b/client/api/openapi.yaml
@@ -279,6 +279,110 @@ paths:
         users with permissions.
       tags:
       - Customers
+  /customers/{customerID}/disclaimers:
+    get:
+      operationId: getCustomerDisclaimers
+      parameters:
+      - description: Optional Request ID allows application developer to trace requests
+          through the systems logs
+        example: rs4f9915
+        explode: false
+        in: header
+        name: X-Request-ID
+        required: false
+        schema:
+          type: string
+        style: simple
+      - description: Moov User ID
+        example: e3cdf999
+        explode: false
+        in: header
+        name: X-User-ID
+        required: false
+        schema:
+          type: string
+        style: simple
+      - description: Customer ID
+        explode: false
+        in: path
+        name: customerID
+        required: true
+        schema:
+          example: e210a9d6-d755-4455-9bd2-9577ea7e1081
+          type: string
+        style: simple
+      responses:
+        200:
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Disclaimers'
+          description: Active disclaimers for the customer
+        400:
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+          description: Customer metadata was not updated, see error(s)
+      summary: Get active disclaimers for the given customer
+      tags:
+      - Customers
+    post:
+      operationId: acceptDisclaimer
+      parameters:
+      - description: Optional Request ID allows application developer to trace requests
+          through the systems logs
+        example: rs4f9915
+        explode: false
+        in: header
+        name: X-Request-ID
+        required: false
+        schema:
+          type: string
+        style: simple
+      - description: Moov User ID
+        example: e3cdf999
+        explode: false
+        in: header
+        name: X-User-ID
+        required: false
+        schema:
+          type: string
+        style: simple
+      - description: Customer ID
+        explode: false
+        in: path
+        name: customerID
+        required: true
+        schema:
+          example: e210a9d6-d755-4455-9bd2-9577ea7e1081
+          type: string
+        style: simple
+      - description: Document type (see Document type for values)
+        explode: true
+        in: query
+        name: type
+        required: true
+        schema:
+          example: DriversLicense
+          type: string
+        style: form
+      responses:
+        200:
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Disclaimer'
+          description: Disclaimer accepted for customer
+        400:
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+          description: Customer disclaimer was not accepted, see error(s)
+      summary: Accept a disclaimer for the given customer
+      tags:
+      - Customers
   /customers/{customerID}/documents:
     get:
       operationId: getCustomerDocuments
@@ -830,6 +934,11 @@ components:
           type: boolean
       type: object
     Disclaimer:
+      example:
+        documentID: 08a184e1
+        ID: 9342f3a7
+        text: Set of conditions
+        acceptedAt: 2016-08-29T09:12:33.001Z
       properties:
         ID:
           description: The unique identifier for this Disclaimer
diff --git a/client/api_customers.go b/client/api_customers.go
index f9e6870..8396975 100644
--- a/client/api_customers.go
+++ b/client/api_customers.go
@@ -28,6 +28,120 @@ var (
 // CustomersApiService CustomersApi service
 type CustomersApiService service
 
+// AcceptDisclaimerOpts Optional parameters for the method 'AcceptDisclaimer'
+type AcceptDisclaimerOpts struct {
+	XRequestID optional.String
+	XUserID    optional.String
+}
+
+/*
+AcceptDisclaimer Accept a disclaimer for the given customer
+ * @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
+ * @param customerID Customer ID
+ * @param type_ Document type (see Document type for values)
+ * @param optional nil or *AcceptDisclaimerOpts - Optional Parameters:
+ * @param "XRequestID" (optional.String) -  Optional Request ID allows application developer to trace requests through the systems logs
+ * @param "XUserID" (optional.String) -  Moov User ID
+@return Disclaimer
+*/
+func (a *CustomersApiService) AcceptDisclaimer(ctx _context.Context, customerID string, type_ string, localVarOptionals *AcceptDisclaimerOpts) (Disclaimer, *_nethttp.Response, error) {
+	var (
+		localVarHTTPMethod   = _nethttp.MethodPost
+		localVarPostBody     interface{}
+		localVarFormFileName string
+		localVarFileName     string
+		localVarFileBytes    []byte
+		localVarReturnValue  Disclaimer
+	)
+
+	// create path and map variables
+	localVarPath := a.client.cfg.BasePath + "/customers/{customerID}/disclaimers"
+	localVarPath = strings.Replace(localVarPath, "{"+"customerID"+"}", _neturl.QueryEscape(fmt.Sprintf("%v", customerID)), -1)
+
+	localVarHeaderParams := make(map[string]string)
+	localVarQueryParams := _neturl.Values{}
+	localVarFormParams := _neturl.Values{}
+
+	localVarQueryParams.Add("type", parameterToString(type_, ""))
+	// to determine the Content-Type header
+	localVarHTTPContentTypes := []string{}
+
+	// set Content-Type header
+	localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
+	if localVarHTTPContentType != "" {
+		localVarHeaderParams["Content-Type"] = localVarHTTPContentType
+	}
+
+	// to determine the Accept header
+	localVarHTTPHeaderAccepts := []string{"application/json"}
+
+	// set Accept header
+	localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
+	if localVarHTTPHeaderAccept != "" {
+		localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
+	}
+	if localVarOptionals != nil && localVarOptionals.XRequestID.IsSet() {
+		localVarHeaderParams["X-Request-ID"] = parameterToString(localVarOptionals.XRequestID.Value(), "")
+	}
+	if localVarOptionals != nil && localVarOptionals.XUserID.IsSet() {
+		localVarHeaderParams["X-User-ID"] = parameterToString(localVarOptionals.XUserID.Value(), "")
+	}
+	r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes)
+	if err != nil {
+		return localVarReturnValue, nil, err
+	}
+
+	localVarHTTPResponse, err := a.client.callAPI(r)
+	if err != nil || localVarHTTPResponse == nil {
+		return localVarReturnValue, localVarHTTPResponse, err
+	}
+
+	localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body)
+	localVarHTTPResponse.Body.Close()
+	if err != nil {
+		return localVarReturnValue, localVarHTTPResponse, err
+	}
+
+	if localVarHTTPResponse.StatusCode >= 300 {
+		newErr := GenericOpenAPIError{
+			body:  localVarBody,
+			error: localVarHTTPResponse.Status,
+		}
+		if localVarHTTPResponse.StatusCode == 200 {
+			var v Disclaimer
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarReturnValue, localVarHTTPResponse, newErr
+			}
+			newErr.model = v
+			return localVarReturnValue, localVarHTTPResponse, newErr
+		}
+		if localVarHTTPResponse.StatusCode == 400 {
+			var v Error
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarReturnValue, localVarHTTPResponse, newErr
+			}
+			newErr.model = v
+			return localVarReturnValue, localVarHTTPResponse, newErr
+		}
+		return localVarReturnValue, localVarHTTPResponse, newErr
+	}
+
+	err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+	if err != nil {
+		newErr := GenericOpenAPIError{
+			body:  localVarBody,
+			error: err.Error(),
+		}
+		return localVarReturnValue, localVarHTTPResponse, newErr
+	}
+
+	return localVarReturnValue, localVarHTTPResponse, nil
+}
+
 // AddCustomerAddressOpts Optional parameters for the method 'AddCustomerAddress'
 type AddCustomerAddressOpts struct {
 	XRequestID optional.String
@@ -358,6 +472,118 @@ func (a *CustomersApiService) GetCustomer(ctx _context.Context, customerID strin
 	return localVarReturnValue, localVarHTTPResponse, nil
 }
 
+// GetCustomerDisclaimersOpts Optional parameters for the method 'GetCustomerDisclaimers'
+type GetCustomerDisclaimersOpts struct {
+	XRequestID optional.String
+	XUserID    optional.String
+}
+
+/*
+GetCustomerDisclaimers Get active disclaimers for the given customer
+ * @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
+ * @param customerID Customer ID
+ * @param optional nil or *GetCustomerDisclaimersOpts - Optional Parameters:
+ * @param "XRequestID" (optional.String) -  Optional Request ID allows application developer to trace requests through the systems logs
+ * @param "XUserID" (optional.String) -  Moov User ID
+@return []Disclaimer
+*/
+func (a *CustomersApiService) GetCustomerDisclaimers(ctx _context.Context, customerID string, localVarOptionals *GetCustomerDisclaimersOpts) ([]Disclaimer, *_nethttp.Response, error) {
+	var (
+		localVarHTTPMethod   = _nethttp.MethodGet
+		localVarPostBody     interface{}
+		localVarFormFileName string
+		localVarFileName     string
+		localVarFileBytes    []byte
+		localVarReturnValue  []Disclaimer
+	)
+
+	// create path and map variables
+	localVarPath := a.client.cfg.BasePath + "/customers/{customerID}/disclaimers"
+	localVarPath = strings.Replace(localVarPath, "{"+"customerID"+"}", _neturl.QueryEscape(fmt.Sprintf("%v", customerID)), -1)
+
+	localVarHeaderParams := make(map[string]string)
+	localVarQueryParams := _neturl.Values{}
+	localVarFormParams := _neturl.Values{}
+
+	// to determine the Content-Type header
+	localVarHTTPContentTypes := []string{}
+
+	// set Content-Type header
+	localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
+	if localVarHTTPContentType != "" {
+		localVarHeaderParams["Content-Type"] = localVarHTTPContentType
+	}
+
+	// to determine the Accept header
+	localVarHTTPHeaderAccepts := []string{"application/json"}
+
+	// set Accept header
+	localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
+	if localVarHTTPHeaderAccept != "" {
+		localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
+	}
+	if localVarOptionals != nil && localVarOptionals.XRequestID.IsSet() {
+		localVarHeaderParams["X-Request-ID"] = parameterToString(localVarOptionals.XRequestID.Value(), "")
+	}
+	if localVarOptionals != nil && localVarOptionals.XUserID.IsSet() {
+		localVarHeaderParams["X-User-ID"] = parameterToString(localVarOptionals.XUserID.Value(), "")
+	}
+	r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes)
+	if err != nil {
+		return localVarReturnValue, nil, err
+	}
+
+	localVarHTTPResponse, err := a.client.callAPI(r)
+	if err != nil || localVarHTTPResponse == nil {
+		return localVarReturnValue, localVarHTTPResponse, err
+	}
+
+	localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body)
+	localVarHTTPResponse.Body.Close()
+	if err != nil {
+		return localVarReturnValue, localVarHTTPResponse, err
+	}
+
+	if localVarHTTPResponse.StatusCode >= 300 {
+		newErr := GenericOpenAPIError{
+			body:  localVarBody,
+			error: localVarHTTPResponse.Status,
+		}
+		if localVarHTTPResponse.StatusCode == 200 {
+			var v []Disclaimer
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarReturnValue, localVarHTTPResponse, newErr
+			}
+			newErr.model = v
+			return localVarReturnValue, localVarHTTPResponse, newErr
+		}
+		if localVarHTTPResponse.StatusCode == 400 {
+			var v Error
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarReturnValue, localVarHTTPResponse, newErr
+			}
+			newErr.model = v
+			return localVarReturnValue, localVarHTTPResponse, newErr
+		}
+		return localVarReturnValue, localVarHTTPResponse, newErr
+	}
+
+	err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+	if err != nil {
+		newErr := GenericOpenAPIError{
+			body:  localVarBody,
+			error: err.Error(),
+		}
+		return localVarReturnValue, localVarHTTPResponse, newErr
+	}
+
+	return localVarReturnValue, localVarHTTPResponse, nil
+}
+
 // GetCustomerDocumentContentsOpts Optional parameters for the method 'GetCustomerDocumentContents'
 type GetCustomerDocumentContentsOpts struct {
 	XRequestID optional.String
diff --git a/client/docs/CustomersApi.md b/client/docs/CustomersApi.md
index 1183e0c..bfc25a3 100644
--- a/client/docs/CustomersApi.md
+++ b/client/docs/CustomersApi.md
@@ -4,9 +4,11 @@ All URIs are relative to *http://localhost:8087*
 
 Method | HTTP request | Description
 ------------- | ------------- | -------------
+[**AcceptDisclaimer**](CustomersApi.md#AcceptDisclaimer) | **Post** /customers/{customerID}/disclaimers | Accept a disclaimer for the given customer
 [**AddCustomerAddress**](CustomersApi.md#AddCustomerAddress) | **Post** /customers/{customerID}/address | Add an Address onto an existing Customer record
 [**CreateCustomer**](CustomersApi.md#CreateCustomer) | **Post** /customers | Create a new customer
 [**GetCustomer**](CustomersApi.md#GetCustomer) | **Get** /customers/{customerID} | Retrieves a Customer object associated with the customer ID.
+[**GetCustomerDisclaimers**](CustomersApi.md#GetCustomerDisclaimers) | **Get** /customers/{customerID}/disclaimers | Get active disclaimers for the given customer
 [**GetCustomerDocumentContents**](CustomersApi.md#GetCustomerDocumentContents) | **Get** /customers/{customerID}/documents/{documentID} | Retrieve the referenced document
 [**GetCustomerDocuments**](CustomersApi.md#GetCustomerDocuments) | **Get** /customers/{customerID}/documents | Get documents for a customer
 [**Ping**](CustomersApi.md#Ping) | **Get** /ping | Ping the Customers service to check if running
@@ -16,6 +18,51 @@ Method | HTTP request | Description
 
 
 
+## AcceptDisclaimer
+
+> Disclaimer AcceptDisclaimer(ctx, customerID, type_, optional)
+Accept a disclaimer for the given customer
+
+### Required Parameters
+
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+**ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc.
+**customerID** | **string**| Customer ID | 
+**type_** | **string**| Document type (see Document type for values) | 
+ **optional** | ***AcceptDisclaimerOpts** | optional parameters | nil if no parameters
+
+### Optional Parameters
+
+Optional parameters are passed through a pointer to a AcceptDisclaimerOpts struct
+
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+
+
+ **xRequestID** | **optional.String**| Optional Request ID allows application developer to trace requests through the systems logs | 
+ **xUserID** | **optional.String**| Moov User ID | 
+
+### Return type
+
+[**Disclaimer**](Disclaimer.md)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+- **Content-Type**: Not defined
+- **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints)
+[[Back to Model list]](../README.md#documentation-for-models)
+[[Back to README]](../README.md)
+
+
 ## AddCustomerAddress
 
 > Customer AddCustomerAddress(ctx, customerID, createAddress, optional)
@@ -147,6 +194,49 @@ No authorization required
 [[Back to README]](../README.md)
 
 
+## GetCustomerDisclaimers
+
+> []Disclaimer GetCustomerDisclaimers(ctx, customerID, optional)
+Get active disclaimers for the given customer
+
+### Required Parameters
+
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+**ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc.
+**customerID** | **string**| Customer ID | 
+ **optional** | ***GetCustomerDisclaimersOpts** | optional parameters | nil if no parameters
+
+### Optional Parameters
+
+Optional parameters are passed through a pointer to a GetCustomerDisclaimersOpts struct
+
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+
+ **xRequestID** | **optional.String**| Optional Request ID allows application developer to trace requests through the systems logs | 
+ **xUserID** | **optional.String**| Moov User ID | 
+
+### Return type
+
+[**[]Disclaimer**](Disclaimer.md)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+- **Content-Type**: Not defined
+- **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints)
+[[Back to Model list]](../README.md#documentation-for-models)
+[[Back to README]](../README.md)
+
+
 ## GetCustomerDocumentContents
 
 > *os.File GetCustomerDocumentContents(ctx, customerID, documentID, optional)
diff --git a/cmd/server/disclaimers.go b/cmd/server/disclaimers.go
index 5358bd0..8bd07b7 100644
--- a/cmd/server/disclaimers.go
+++ b/cmd/server/disclaimers.go
@@ -46,6 +46,7 @@ func getDisclaimerID(w http.ResponseWriter, r *http.Request) string {
 func getCustomerDisclaimers(logger log.Logger, repo disclaimerRepository) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		w = wrapResponseWriter(logger, w, r)
+		w.Header().Set("Content-Type", "application/json; charset=utf-8")
 
 		customerID := getCustomerID(w, r)
 		if customerID == "" {
@@ -58,7 +59,6 @@ func getCustomerDisclaimers(logger log.Logger, repo disclaimerRepository) http.H
 			return
 		}
 
-		w.Header().Set("Content-Type", "application/json; charset=utf-8")
 		w.WriteHeader(http.StatusOK)
 		json.NewEncoder(w).Encode(disclaimers)
 	}
@@ -67,6 +67,7 @@ func getCustomerDisclaimers(logger log.Logger, repo disclaimerRepository) http.H
 func acceptDisclaimer(logger log.Logger, repo disclaimerRepository) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		w = wrapResponseWriter(logger, w, r)
+		w.Header().Set("Content-Type", "application/json; charset=utf-8")
 
 		customerID, disclaimerID := getCustomerID(w, r), getDisclaimerID(w, r)
 		if customerID == "" || disclaimerID == "" {
@@ -77,7 +78,15 @@ func acceptDisclaimer(logger log.Logger, repo disclaimerRepository) http.Handler
 			moovhttp.Problem(w, err)
 			return
 		}
+
+		disclaimer, err := repo.getCustomerDisclaimer(customerID, disclaimerID)
+		if err != nil {
+			moovhttp.Problem(w, err)
+			return
+		}
+
 		w.WriteHeader(http.StatusOK)
+		json.NewEncoder(w).Encode(disclaimer)
 	}
 }
 
@@ -89,6 +98,7 @@ type createDisclaimerRequest struct {
 func createDisclaimer(logger log.Logger, disclaimRepo disclaimerRepository, docRepo documentRepository) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		w = wrapResponseWriter(logger, w, r)
+		w.Header().Set("Content-Type", "application/json; charset=utf-8")
 
 		if r.Method != "POST" {
 			moovhttp.Problem(w, fmt.Errorf("unsupported HTTP verb %s", r.Method))
@@ -122,7 +132,6 @@ func createDisclaimer(logger log.Logger, disclaimRepo disclaimerRepository, docR
 			return
 		}
 
-		w.Header().Set("Content-Type", "application/json; charset=utf-8")
 		w.WriteHeader(http.StatusOK)
 		json.NewEncoder(w).Encode(disclaimer)
 	}
@@ -145,6 +154,7 @@ func documentExistsForCustomer(customerID string, req createDisclaimerRequest, d
 }
 
 type disclaimerRepository interface {
+	getCustomerDisclaimer(customerID, disclaimerID string) (*client.Disclaimer, error)
 	getCustomerDisclaimers(customerID string) ([]*client.Disclaimer, error)
 	acceptDisclaimer(customerID, disclaimerID string) error
 	insertDisclaimer(text, documentID string) (*client.Disclaimer, error)
@@ -159,31 +169,52 @@ func (r *sqlDisclaimerRepository) close() error {
 	return r.db.Close()
 }
 
-func (r *sqlDisclaimerRepository) getCustomerDisclaimers(customerID string) ([]*client.Disclaimer, error) {
+func (r *sqlDisclaimerRepository) getCustomerDisclaimer(customerID, disclaimerID string) (*client.Disclaimer, error) {
 	query := `select d.disclaimer_id, d.text, d.document_id, da.accepted_at from disclaimers as d
 left outer join disclaimer_acceptances as da on d.disclaimer_id = da.disclaimer_id
-where d.deleted_at is null;`
+where d.deleted_at is null and d.disclaimer_id = ? limit 1;`
 	stmt, err := r.db.Prepare(query)
 	if err != nil {
 		return nil, err
 	}
 	defer stmt.Close()
 
-	rows, err := stmt.Query()
+	var acceptedAt *time.Time
+	var d client.Disclaimer
+
+	if err := stmt.QueryRow(disclaimerID).Scan(&d.ID, &d.Text, &d.DocumentID, &acceptedAt); err != nil {
+		if err == sql.ErrNoRows {
+			return nil, nil
+		}
+		return nil, err
+	}
+	if acceptedAt != nil && !acceptedAt.IsZero() {
+		d.AcceptedAt = *acceptedAt
+	}
+
+	return &d, nil
+}
+
+func (r *sqlDisclaimerRepository) getCustomerDisclaimers(customerID string) ([]*client.Disclaimer, error) {
+	query := `select disclaimer_id from disclaimers where deleted_at is null;`
+	stmt, err := r.db.Prepare(query)
 	if err != nil {
 		return nil, err
 	}
-	defer rows.Close()
 
 	var out []*client.Disclaimer
+	rows, err := stmt.Query()
+	if err != nil {
+		return nil, err
+	}
 	for rows.Next() {
-		disc := &client.Disclaimer{}
-		var acceptedAt *time.Time
-		if err := rows.Scan(&disc.ID, &disc.Text, &disc.DocumentID, &acceptedAt); err != nil {
+		var disclaimerID string
+		if err := rows.Scan(&disclaimerID); err != nil {
 			return nil, err
 		}
-		if acceptedAt != nil && !acceptedAt.IsZero() {
-			disc.AcceptedAt = *acceptedAt
+		disc, err := r.getCustomerDisclaimer(customerID, disclaimerID)
+		if err != nil {
+			return nil, err
 		}
 		out = append(out, disc)
 	}
diff --git a/openapi.yaml b/openapi.yaml
index c480d20..bd3766e 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -231,6 +231,88 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/Error'
+  /customers/{customerID}/disclaimers:
+    get:
+      tags: [Customers]
+      summary: Get active disclaimers for the given customer
+      operationId: getCustomerDisclaimers
+      parameters:
+        - name: X-Request-ID
+          in: header
+          description: Optional Request ID allows application developer to trace requests through the systems logs
+          example: rs4f9915
+          schema:
+            type: string
+        - name: X-User-ID
+          in: header
+          description: Moov User ID
+          example: e3cdf999
+          schema:
+            type: string
+        - name: customerID
+          in: path
+          description: Customer ID
+          required: true
+          schema:
+            type: string
+            example: e210a9d6-d755-4455-9bd2-9577ea7e1081
+      responses:
+        '200':
+          description: Active disclaimers for the customer
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Disclaimers'
+        '400':
+          description: Customer metadata was not updated, see error(s)
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+    post:
+      tags: [Customers]
+      summary: Accept a disclaimer for the given customer
+      operationId: acceptDisclaimer
+      parameters:
+        - name: X-Request-ID
+          in: header
+          description: Optional Request ID allows application developer to trace requests through the systems logs
+          example: rs4f9915
+          schema:
+            type: string
+        - name: X-User-ID
+          in: header
+          description: Moov User ID
+          example: e3cdf999
+          schema:
+            type: string
+        - name: customerID
+          in: path
+          description: Customer ID
+          required: true
+          schema:
+            type: string
+            example: e210a9d6-d755-4455-9bd2-9577ea7e1081
+        - name: type
+          in: query
+          description: Document type (see Document type for values)
+          required: true
+          schema:
+            type: string
+            example: DriversLicense
+      responses:
+        '200':
+          description: Disclaimer accepted for customer
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Disclaimer'
+        '400':
+          description: Customer disclaimer was not accepted, see error(s)
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
   /customers/{customerID}/documents:
     post:
       tags: [Customers]


Test patch:
diff --git a/cmd/server/disclaimers_test.go b/cmd/server/disclaimers_test.go
index 7647f90..ae267c3 100644
--- a/cmd/server/disclaimers_test.go
+++ b/cmd/server/disclaimers_test.go
@@ -29,6 +29,16 @@ type testDisclaimerRepository struct {
 	err         error
 }
 
+func (r *testDisclaimerRepository) getCustomerDisclaimer(customerID, documentID string) (*client.Disclaimer, error) {
+	if r.err != nil {
+		return nil, r.err
+	}
+	if len(r.disclaimers) > 0 {
+		return r.disclaimers[0], nil
+	}
+	return nil, nil
+}
+
 func (r *testDisclaimerRepository) getCustomerDisclaimers(customerID string) ([]*client.Disclaimer, error) {
 	if r.err != nil {
 		return nil, r.err
@@ -65,6 +75,32 @@ func TestDisclaimers__getDisclaimerID(t *testing.T) {
 	}
 }
 
+func TestDisclaimers__getCustomerDisclaimer(t *testing.T) {
+	check := func(t *testing.T, repo *sqlDisclaimerRepository) {
+		defer repo.close()
+
+		customerID, disclaimerID := base.ID(), base.ID()
+
+		disclaimer, err := repo.getCustomerDisclaimer(customerID, disclaimerID)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if disclaimer != nil {
+			t.Errorf("expected no disclaimer")
+		}
+	}
+
+	// SQLite tests
+	sqliteDB := database.CreateTestSqliteDB(t)
+	defer sqliteDB.Close()
+	check(t, &sqlDisclaimerRepository{sqliteDB.DB, log.NewNopLogger()})
+
+	// MySQL tests
+	mysqlDB := database.CreateTestMySQLDB(t)
+	defer mysqlDB.Close()
+	check(t, &sqlDisclaimerRepository{mysqlDB.DB, log.NewNopLogger()})
+}
+
 func TestDisclaimers__getCustomerDisclaimers(t *testing.T) {
 	repo := &testDisclaimerRepository{
 		disclaimers: []*client.Disclaimer{