authnet-clj

0.1.0


Native Clojure client for Authorize.Net JSON API

dependencies

org.clojure/clojure
1.8.0
clj-http
3.10.0
cheshire
5.9.0



(this space intentionally left almost blank)
 

ARB - Automatic Recurring Billing

This namespace implements the functions require to list, get, create, and update recurring billing subscriptions.

(ns authnet-clj.arb
  (:require [authnet-clj.core :refer :all] ))

Records to facilitate subscription update.

The following example illustrates the basic structure for a subscription payment update. The records defined below reflect each of the segments of this structure.


{
    "ARBUpdateSubscriptionRequest": {
        "merchantAuthentication": {
            "name": "********",
            "transactionKey": "*******************"
        },
        "refId": "123456",
        "subscriptionId": "100748",
        "subscription": {
            "payment": {
                "creditCard": {
                    "cardNumber": "4111111111111111",
                    "expirationDate": "2020-12"
                }
            }
        }
    }
}

The billing address associated with a credit card update

(defrecord billTo [firstName lastName address city state zip])

Credit card record with card number and expiration date attributes

(defrecord creditCard [cardNumber expirationDate])

A payment segnment includes a credit card map

(defrecord payment [^creditCard creditCard])

A subscription update can include a new CC as well as a new billTo (name, address). The update-subscription function has two arities to support these two scenarios.

(defrecord subscription [^payment payment ^billTo billTo])

Makes the map required to submit a subscription payment method update request.

(defn make-subscription-payment-update
 ([credit-card expiration-date]
  (->subscription (->payment (->creditCard credit-card expiration-date))))
 ([credit-card expiration-date f-name l-name address city state zip]
  (->subscription (->payment (->creditCard credit-card expiration-date)) (->billTo f-name l-name address city state zip))))

Concrete Subscription Request Functions


Retrieves the subscription status for the specified subscription.

(defn get-subscription-status
  [subscription-id]
  (let [creds (get-credentials)
        url (get-endpoint creds)]
    (do-request url (build-request :ARBGetSubscriptionStatusRequest {:subscriptionId subscription-id} ) )))

Retrieves the details of the specified subscription.

(defn get-subscription
  [subscription-id]
  (let [creds (get-credentials)
        url (get-endpoint creds)]
    (do-request url (build-request :ARBGetSubscriptionRequest {:subscriptionId subscription-id} ) )))

Retrieves a sequence of subscriptions in the specified status.

Inputs:

     search-type: cardExpiringThisMonth
                  subscriptionActive
                  subscriptionInactive
                  subscriptionExpiringThisMonth
     

(defn get-subscriptions
  [search-type]
  (let [creds (get-credentials)
        url (get-endpoint creds)]
    (do-request url (build-request :ARBGetSubscriptionListRequest {:searchType search-type }))))

Updates the Credit Card number and Expirate Date of the specified subscription. It can also update the Credit Card holder's name and address if 9-arity function is invoked.

(defn update-subscription
  ([subscription-id cc-number cc-expiration]
   (let [creds (get-credentials)
         url (get-endpoint creds)
         ]
     (do-request url (build-request :ARBUpdateSubscriptionRequest
                                    {:subscriptionId subscription-id
                                     :subscription (make-subscription-payment-update cc-number cc-expiration )}))))
  ([subscription-id cc-number cc-expiration fname lname address city state zip]
   (let [creds (get-credentials)
         url (get-endpoint creds)
         ]
     (do-request url (build-request :ARBUpdateSubscriptionRequest
                                    {:subscriptionId subscription-id
                                     :subscription (make-subscription-payment-update cc-number cc-expiration fname lname address city state zip)})))))

Makes a request to cancel the identified subscription.

(defn cancel-subscription
  [subscription-id]
  (let [creds (get-credentials)
        url (get-endpoint creds)
        ]
    (do-request url  (build-request :ARBCancelSubscriptionRequest {:subscriptionId subscription-id}))))
 

Customer Profile

This namespace implements the functions related to retrieving and updating customer profiles.

(ns authnet-clj.profiles
  (:require [authnet-clj.core :refer :all]))

Retrieves a customer profile.

(defn get-customer-profile
  [customer-profile-id]
  (let [creds (get-credentials)
        url (get-endpoint creds)
        ]
    (do-request url (build-request :getCustomerProfileRequest {:customerProfileId customer-profile-id :includeIssuerInfo "true"}))))

Lists all payment profiles that expire in the specified month. month: "2020-12"

(defn list-expiring-customers-payment-profiles
  [month]
  (let [creds (get-credentials)
        url (get-endpoint creds)
        ]
    (do-request url (build-request :getCustomerPaymentProfileListRequest {:searchType "cardsExpiringInMonth" :month month}))))

Lists all customers profiles IDs.

(defn list-customers-profile-ids
  []
  (let [creds (get-credentials)
        url (get-endpoint creds)
        ]
    (do-request url (build-request :getCustomerProfileIdsRequest {} ))))
 

Core Common functions

This namespace holds core functions used by each of the (ARB, CIM, Trans) request-specific namespaces.

(ns authnet-clj.core
  (:require [cheshire.core :refer :all ]
            [clj-http.client :as client])
  (:gen-class))

Defines the sandbox and production end-points

(def endpoints
  { :sandbox "https://apitest.authorize.net/xml/v1/request.api"
    :production "https://api.authorize.net/xml/v1/request.api" })

Retrieves the Authorize.net credentials from environment variables.

(defn get-credentials
  []
  (let [name (System/getenv "AUTHORIZENET_LOGIN_ID")
        key (System/getenv "AUTHORIZENET_TRANSACTION_KEY")
        gateway (System/getenv "AUTHORIZENET_GATEWAY")
        ]
    (if (nil? name)
      (throw (ex-info "Environment variable AUTHORIZENET_LOGIN_ID is missing" {})))
    (if (nil? key)
      (throw (ex-info "Environment variable AUTHORIZENET_TRANSACTION_KEY is missing" {})))
    (if (nil? gateway)
      (throw (ex-info "Environment variable AUTHORIZENET_GATEWAY is missing" {})))
  {:name name
   :transactionKey key
   :gateway gateway } ))

Helper functions to get each Authorize.net credential variable.

(defn get-name [creds] (:name (get-credentials)))
(defn get-transaction-key [creds] (:transactionKey (get-credentials)))
(defn get-gateway [creds] (keyword (:gateway (get-credentials)) )  )
(defn get-endpoint [creds]
  ((get-gateway creds) endpoints))

Common transaction request maps generators. These are used within each of the API segements (ARB, Customer Profile, etc.)

(defn get-marchant-authentication-map
  [creds]
  {:merchantAuthentication
   {:name (:name creds) :transactionKey (:transactionKey creds)}})

Generates the complete transaction request map including the transaction name along with the merchant Authentication segment followed by the transaction payload.

Inputs:


transaction-name: a keyword, such as :ARBGetSubscriptionstatusrequest that identifies the transactions to be submitted.

merchantauth-map: a map with a single key :merchantAuthentication and which value is a map containing the API Login ID and API Transaction Key.

Note: Use the get-merchant-authentication-map helper to generate the merchantauth-map.

payload-map: a map of k/v that is specific to the transaction (transaction-name). (https://developer.authorize.net/api/reference)

Output:


A map similar to the one depicted below, but with the k/v that are specific to a transaction after the merchantAuthentication map.

  
  {
    'ARBGetSubscriptionStatusRequest': {
        'merchantAuthentication': {
            'name': '<AUTHORIZINET_LOGIN_ID>',
            'transactionKey': '<AUTHORIZENET_TRANSACTION_KEY>'
        },
        'refId': '123456',
        'subscriptionId': '100748'
    }
  }
  
  

(defn get-transaction-map
  [merchantauth-map transaction-name payload-map]
  {transaction-name  (merge  merchantauth-map payload-map) })

Request builder responsible for creating a properly formed request map including the merchant Authentication segment.

(defn build-request
  [request-type request-payload]
  (-> (get-credentials)
      (get-marchant-authentication-map)
      (get-transaction-map request-type request-payload )))

Request producer responsible for issuing the POST request to the Authorize.Net end-point specified via the environment variables.

Note: Authorize.Net returns BOM (Byte Order Marker) byte that screws up the JSON parser. The combination of reading the body as a stream and removing the BOM  does the trick.

(defn do-request
 "Request producer responsible for issuing the POST request to the Authorize.Net end-point
  specified via the environment variables.
  Note: Authorize.Net returns BOM (Byte Order Marker) byte that screws
  up the JSON parser. The combination of reading the body as
  a stream and removing the BOM \uFEFF does the trick.
  "
  [url query]
  (let [res (client/post url
               {:body (generate-string query )
                :accept :json
                :body-encoding "UTF-16"
                :as :stream
                :throw-exceptions? false
                :content-type :json}) ]
    (-> res
        (:body)
        (slurp)
        (clojure.string/replace #"\r\n|\n|\r|\uFEFF" "")
        (parse-string true))))