Add command SEPAPREORDER.
authorWerner Koch <wk@gnupg.org>
Mon, 9 Mar 2015 19:35:26 +0000 (20:35 +0100)
committerWerner Koch <wk@gnupg.org>
Mon, 9 Mar 2015 19:36:02 +0000 (20:36 +0100)
* configure.ac (SQLITE3): Require SQLite 3.
* src/preorder.c, src/preorder.h: New.
* src/t-preorder.c: New.
* src/connection.c (cmd_sepapreorder): New.
(connection_handler): Add new command.

.gitignore
configure.ac
doc/api-ref.org
src/Makefile.am
src/connection.c
src/preorder.c [new file with mode: 0644]
src/preorder.h [new file with mode: 0644]
src/t-preorder.c [new file with mode: 0644]

index 0b41e6d..a635dba 100644 (file)
@@ -38,3 +38,4 @@
 /src/libcommonpth.a
 /src/ppipnhd
 /src/payproc-stat
+/src/t-preorder
index 1e09d6b..8ae4f80 100644 (file)
@@ -125,7 +125,6 @@ AC_PROG_INSTALL
 AC_PROG_LN_S
 AC_PROG_RANLIB
 AC_CHECK_TOOL(AR, ar, :)
-AC_CHECK_TOOL(WINDRES, windres, :)
 AC_ISC_POSIX
 AC_SYS_LARGEFILE
 
@@ -188,6 +187,25 @@ else
 *** $tmp]])
 fi
 
+#
+# SQLite
+#
+PKG_CHECK_MODULES([SQLITE3],[sqlite3 >= 3.5],
+                            [have_sqlite3=yes],
+                            [have_sqlite3=no])
+if test "$have_sqlite3" = "yes"; then
+  AC_SUBST([SQLITE3_CFLAGS])
+  AC_SUBST([SQLITE3_LIBS])
+  :
+else
+  tmp=$(echo "$SQLITE3_PKG_ERRORS" | tr '\n' '\v' | sed 's/\v/\n*** /g')
+  AC_MSG_WARN([[
+***
+*** SQLite3 not found
+***
+*** $tmp]])
+fi
+
 
 AC_MSG_NOTICE([checking for networking options])
 
@@ -416,6 +434,13 @@ if test "$have_gnutls" = "no"; then
 *** (at least version $NEED_GNUTLS_VERSION is required.)
 ***]])
 fi
+if test "$have_sqlite3" = "no"; then
+   die=yes
+   AC_MSG_NOTICE([[
+***
+*** You need SQLite3 to build this program.
+***]])
+fi
 
 if test "$die" = "yes"; then
     AC_MSG_ERROR([[
index 97351ba..3350140 100644 (file)
@@ -111,6 +111,33 @@ amount converted to an Euro value.  This conversion is done using the
 reference data retrieved via cron job.  It may be different from the
 conversion done by the payment service provider.
 
+** PPCHECKOUT
+
+PayPal Checkout.  See the source for details. FIXME.
+
+** SEPAPREORDER
+
+#+begin_example
+SEPAPREORDER
+Amount: 17.3
+
+OK
+Currency: Eur
+Amount: 17.30
+SEPA-Ref: GYT3L-27
+#+end_example
+
+The error return is similar to CHARGECARD.
+
+This commands adds a preorder record for a SEPA payment into the
+preorder database.  That record will be removed after 30 days if it
+has not been used.  A command line tool can be used to match a
+received payment with this record and create a final log record.  Note
+that a Currency is not required because SEPA does only allow Euro.
+The SEPA-Ref is a short random string used to index that record.  The
+additional number is used to find an entry in the preorder db in case
+of a typos in the first string.
+
 
 ** SESSION
 
index 57c8fd0..86d7d48 100644 (file)
@@ -56,14 +56,15 @@ payprocd_SOURCES = \
        tlssupport.c tlssupport.h \
        cred.c cred.h \
        journal.c journal.h \
+       preorder.c preorder.h \
        session.c session.h \
        $(common_headers) \
        $(utility_sources)
 payprocd_CFLAGS = $(GPG_ERROR_CFLAGS) $(NPTH_CFLAGS) $(LIBGCRYPT_CFLAGS) \
-                  $(LIBGNUTLS_CFLAGS)
+                  $(LIBGNUTLS_CFLAGS) $(SQLITE3_CFLAGS)
 payprocd_LDADD = -lm libcommonpth.a \
                   $(GPG_ERROR_LIBS) $(NPTH_LIBS) $(LIBGCRYPT_LIBS) \
-                  $(LIBGNUTLS_LIBS)
+                  $(LIBGNUTLS_LIBS) $(SQLITE3_LIBS)
 
 payproc_jrnl_SOURCES = \
         payproc-jrnl.c \
@@ -77,7 +78,7 @@ ppipnhd_SOURCES = ppipnhd.c
 ppipnhd_CFLAGS =
 ppipnhd_LDADD =
 
-module_tests = t-connection
+module_tests = t-connection t-preorder
 
 AM_CFLAGS = $(GPG_ERROR_CFLAGS)
 LDADD  = -lm libcommon.a $(GPG_ERROR_LIBS)
@@ -93,6 +94,11 @@ t_http_CFLAGS  = $(t_common_cflags)
 t_http_LDADD   = $(t_common_ldadd)
 
 t_connection_SOURCES = t-connection.c stripe.c paypal.c paypal-ipn.c \
+                      preorder.c \
                        journal.c session.c currency.c $(t_common_sources)
-t_connection_CFLAGS  = $(t_common_cflags) $(LIBGCRYPT_CFLAGS)
-t_connection_LDADD   = $(t_common_ldadd) $(LIBGCRYPT_LIBS)
+t_connection_CFLAGS  = $(t_common_cflags) $(LIBGCRYPT_CFLAGS) $(SQLITE3_CFLAGS)
+t_connection_LDADD   = $(t_common_ldadd) $(LIBGCRYPT_LIBS) $(SQLITE3_LIBS)
+
+t_preorder_SOURCES = t-preorder.c $(t_common_sources) journal.c currency.c
+t_preorder_CFLAGS  = $(t_common_cflags) $(LIBGCRYPT_CFLAGS) $(SQLITE3_CFLAGS)
+t_preorder_LDADD   = $(t_common_ldadd) $(LIBGCRYPT_LIBS) $(SQLITE3_LIBS)
index 20c126c..c4b67af 100644 (file)
@@ -32,6 +32,7 @@
 #include "journal.h"
 #include "session.h"
 #include "currency.h"
+#include "preorder.h"
 #include "connection.h"
 
 /* Maximum length of an input line.  */
@@ -929,6 +930,99 @@ cmd_ppcheckout (conn_t conn, char *args)
 
 
 \f
+/* The SEPAPREORDER command adds a preorder record for a SEPA payment
+   into the preorder database.  The following values are expected in
+   the dataitems:
+
+   Amount:     The amount to charge with optional decimal fraction.
+   Currency:   If given its value must be EUR.
+   Desc:       Optional description of the charge.
+   Email:      Optional contact mail address of the customer
+   Meta[NAME]: Meta data further described by NAME.  This is used to convey
+               application specific data to the log file.
+
+   On success these items are returned:
+
+   SEPA-Ref:   A string to be returned to the caller.
+   Amount:     The reformatted amount
+   Currency:   The value "EUR"
+   _timestamp: The timestamp as written to the preorder db.
+
+ */
+static gpg_error_t
+cmd_sepapreorder (conn_t conn, char *args)
+{
+  gpg_error_t err;
+  keyvalue_t dict = conn->dataitems;
+  keyvalue_t kv;
+  const char *s;
+  unsigned int cents;
+  char *buf = NULL;
+
+  (void)args;
+
+  /* Get currency and amount.  */
+  s = keyvalue_get (dict, "Currency");
+  if (!s)
+    {
+      err = keyvalue_put (&conn->dataitems, "Currency", "EUR");
+      if (err)
+        goto leave;
+    }
+  else if (strcasecmp (s, "EUR"))
+    {
+      set_error (INV_VALUE, "Currency must be \"EUR\" if given");
+      goto leave;
+    }
+
+  s = keyvalue_get_string (dict, "Amount");
+  if (!*s || !(cents = convert_amount (s, 2)))
+    {
+      set_error (MISSING_VALUE, "Amount missing or invalid");
+      goto leave;
+    }
+  err = keyvalue_putf (&conn->dataitems, "_amount", "%u", cents);
+  dict = conn->dataitems;
+  if (err)
+    goto leave;
+  buf = reconvert_amount (keyvalue_get_int (conn->dataitems, "_amount"), 2);
+  if (!buf)
+    {
+      err = gpg_error_from_syserror ();
+      conn->errdesc = "error converting _amount";
+      goto leave;
+    }
+  err = keyvalue_put (&conn->dataitems, "Amount", buf);
+  if (err)
+    goto leave;
+
+  /* Note that the next function does not only store the record but
+     also creates the SEPA-Ref value and puts it into dataitems.  This
+     is to make sure SEPA-Ref is a unique key for the preorder db.  */
+  err = preorder_store_record (&conn->dataitems);
+
+ leave:
+  if (err)
+    {
+      es_fprintf (conn->stream, "ERR %d (%s)\n", err,
+                  conn->errdesc? conn->errdesc : gpg_strerror (err));
+      write_data_line (keyvalue_find (conn->dataitems, "failure"),
+                       conn->stream);
+      write_data_line (keyvalue_find (conn->dataitems, "failure-mesg"),
+                       conn->stream);
+    }
+  else
+    es_fprintf (conn->stream, "OK\n");
+  for (kv = conn->dataitems; kv; kv = kv->next)
+    if (kv->name[0] >= 'A' && kv->name[0] < 'Z')
+      write_data_line (kv, conn->stream);
+
+  es_free (buf);
+  return err;
+}
+
+
+\f
 /* The CHECKAMOUNT command checks whether a given amount is within the
    configured limits for payment.  It may eventually provide
    additional options.  The following values are expected in the
@@ -1104,6 +1198,8 @@ connection_handler (conn_t conn)
     err = cmd_chargecard (conn, cmdargs);
   else if ((cmdargs = has_leading_keyword (conn->command, "PPCHECKOUT")))
     err = cmd_ppcheckout (conn, cmdargs);
+  else if ((cmdargs = has_leading_keyword (conn->command, "SEPAPREORDER")))
+    err = cmd_sepapreorder (conn, cmdargs);
   else if ((cmdargs = has_leading_keyword (conn->command, "CHECKAMOUNT")))
     err = cmd_checkamount (conn, cmdargs);
   else if ((cmdargs = has_leading_keyword (conn->command, "PPIPNHD")))
diff --git a/src/preorder.c b/src/preorder.c
new file mode 100644 (file)
index 0000000..2e59010
--- /dev/null
@@ -0,0 +1,368 @@
+/* preorder.c - Access to the preorder database.
+ * Copyright (C) 2015 g10 Code GmbH
+ *
+ * This file is part of Payproc.
+ *
+ * Payproc is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Payproc is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* The Database used for preorders is pretty simple:  Just a single table:
+
+   CREATE TABLE preorder (
+     ref   TEXT NOT NULL PRIMARY KEY,  -- The "ABCDE" part of ABCDE-NN.
+     refnn INTEGER NOT NULL,           -- The "NN"    part of ABCDE-NN
+     created TEXT NOT NULL,            -- Timestamp
+     paid TEXT,                        -- Timestamp of last payment
+     npaid INTEGER NOT NULL,           -- Total number of payments
+     amount TEXT NOT NULL,             -- with decimal digit; thus TEXT.
+     currency TEXT NOT NULL,
+     desc TEXT,   -- Description of the order
+     email TEXT,  -- Optional mail address.
+     meta TEXT    -- Using the format from the journal.
+   )
+
+
+  Expiring entries can be done using
+
+     DELETE from preorder
+     WHERE julianday(created) < julianday('now', '-30 days');
+
+  this has not been implemneted but should be done at startup and
+  every day here.
+
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <npth.h>
+#include <gcrypt.h>
+#include <sqlite3.h>
+
+#include "util.h"
+#include "logging.h"
+#include "payprocd.h"
+#include "journal.h"  /* Temporary for meta_field_to_string.  */
+#include "preorder.h"
+
+
+#define DB_DATETIME_SIZE 20 /* "1970-01-01 12:00:00" */
+
+
+/* The name of the preorder database file.  */
+static const char preorder_db_fname[] = "/var/lib/payproc/preorder.db";
+
+/* The database handle used for the preorder database.  This handle
+   may only used after a successful open_preorder_db call and not
+   after a close_preorder_db call.  The lock variable is maintained by
+   the mentioned open and close functions. */
+static sqlite3 *preorder_db;
+static npth_mutex_t preorder_db_lock = NPTH_MUTEX_INITIALIZER;
+
+/* This is a prepared statement for the INSERT operation.  It is
+   protected by preorder_db_lock.  */
+static sqlite3_stmt *preorder_insert_stmt;
+
+
+
+\f
+/* Create a SEPA-Ref field and store it in BUFFER.  The format is:
+
+     AAAAA-NN
+
+  with AAAAA being uppercase letters or digits and NN a value between
+  10 and 99.  Thus the entire length of the returned string is 8.  We
+  use a base 28 alphabet for the A values with the first A restricted
+  to a letter.  Some letters are left out because they might be
+  misrepresented due to OCR scanning.  There are about 11 million
+  different values for AAAAA. */
+static void
+make_sepa_ref (char *buffer, size_t bufsize)
+{
+  static char codes[28] = { 'A', 'B', 'C', 'D', 'E', 'G', 'H', 'J',
+                            'K', 'L', 'N', 'R', 'S', 'T', 'W', 'X',
+                            'Y', 'Z', '0', '1', '2', '3', '4', '5',
+                            '6', '7', '8', '9' };
+  unsigned char nonce[5];
+  int i;
+  unsigned int n;
+
+  if (bufsize < 9)
+    BUG ();
+
+  gcry_create_nonce (nonce, sizeof nonce);
+  buffer[0] = codes[nonce[0] % 18];
+  for (i=1; i < 5; i++)
+    buffer[i] = codes[nonce[i] % 28];
+  buffer[5] = '-';
+  n = (((unsigned int)nonce[0] << 24) | (nonce[1] << 16)
+       | (nonce[2] << 8) | nonce[3]);
+  i = 10 + (n % 90);
+  buffer [6] = '0' + i / 10;
+  buffer [7] = '0' + i % 10;
+  buffer [8] = 0;
+}
+
+
+/* Given a buffer of size DB_DATETIME_SIZE put the current time into it.  */
+static char *
+db_datetime_now (char *buffer)
+{
+#if DB_DATETIME_SIZE != TIMESTAMP_SIZE + 4
+# error mismatching timestamp sizes
+#endif
+  get_current_time (buffer);
+  /* "19700101T120000" to
+     "1970-01-01 12:00:00" */
+  buffer[19] = 0;
+  buffer[18] = buffer[14];
+  buffer[17] = buffer[13];
+  buffer[16] = ':';
+  buffer[15] = buffer[12];
+  buffer[14] = buffer[11];
+  buffer[13] = ':';
+  buffer[12] = buffer[10];
+  buffer[11] = buffer[9];
+  buffer[10] = ' ';
+  buffer[9] = buffer[7];
+  buffer[8] = buffer[6];
+  buffer[7] = '-';
+  buffer[6] = buffer[5];
+  buffer[5] = buffer[4];
+  buffer[4] = '-';
+
+  return buffer;
+}
+
+
+
+
+/* Relinquishes the lock on the database handle and if DO_CLOSE is
+   true also close the database handle.  Note that we usually keep the
+   database open for the lifetime of the process.  */
+static void
+close_preorder_db (int do_close)
+{
+  int res;
+
+  if (do_close && preorder_db)
+    {
+      res = sqlite3_close (preorder_db);
+      if (res == SQLITE_BUSY)
+        {
+          sqlite3_finalize (preorder_insert_stmt);
+          preorder_insert_stmt = NULL;
+          res = sqlite3_close (preorder_db);
+        }
+      if (res)
+        log_error ("failed to close the preorder db: %s\n",
+                   sqlite3_errstr (res));
+      preorder_db = NULL;
+    }
+
+  res = npth_mutex_unlock (&preorder_db_lock);
+  if (res)
+    log_fatal ("failed to release preorder db lock: %s\n",
+               gpg_strerror (gpg_error_from_errno (res)));
+}
+
+
+/* This function opens or creates the preorder database.  If the
+   database is already open it merly takes a lock ion the handle. */
+static gpg_error_t
+open_preorder_db (void)
+{
+  int res;
+  sqlite3_stmt *stmt;
+
+  res = npth_mutex_lock (&preorder_db_lock);
+  if (res)
+    log_fatal ("failed to acquire preorder db lock: %s\n",
+               gpg_strerror (gpg_error_from_errno (res)));
+  if (preorder_db)
+    return 0; /* Good: Already open.  */
+
+  /* Database has not yet been opened.  Open or create it, make sure
+     the tables exist, and prepare the required statements.  We use
+     out own locking instead of the more complex serialization sqlite
+     would have to do. */
+
+  res = sqlite3_open_v2 (preorder_db_fname,
+                         &preorder_db,
+                         (SQLITE_OPEN_READWRITE
+                          | SQLITE_OPEN_CREATE
+                          | SQLITE_OPEN_NOMUTEX),
+                         NULL);
+  if (res)
+    {
+      log_error ("error opening '%s': %s\n",
+                 preorder_db_fname, sqlite3_errstr (res));
+      close_preorder_db (1);
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+  sqlite3_extended_result_codes (preorder_db, 1);
+
+
+  /* Create the tables if needed.  */
+  res = sqlite3_prepare_v2 (preorder_db,
+                            "CREATE TABLE IF NOT EXISTS preorder ("
+                            "ref      TEXT NOT NULL PRIMARY KEY,"
+                            "refnn    INTEGER NOT NULL,"
+                            "created  TEXT NOT NULL,"
+                            "paid TEXT,"
+                            "npaid INTEGER NOT NULL,"
+                            "amount   TEXT NOT NULL,"
+                            "currency TEXT NOT NULL,"
+                            "desc     TEXT,"
+                            "email    TEXT,"
+                            "meta     TEXT"
+                            ")",
+                            -1, &stmt, NULL);
+  if (res)
+    {
+      log_error ("error creating preorder table (prepare): %s\n",
+                 sqlite3_errstr (res));
+      close_preorder_db (1);
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  res = sqlite3_step (stmt);
+  sqlite3_finalize (stmt);
+  if (res != SQLITE_DONE)
+    {
+      log_error ("error creating preorder table: %s\n", sqlite3_errstr (res));
+      close_preorder_db (1);
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  /* Prepare an insert statement.  */
+  res = sqlite3_prepare_v2 (preorder_db,
+                            "INSERT INTO preorder VALUES ("
+                            "?1,?2,?3,NULL,0,?4,?5,?6,?7,?8"
+                            ")",
+                            -1, &stmt, NULL);
+  if (res)
+    {
+      log_error ("error preparing insert statement: %s\n",
+                 sqlite3_errstr (res));
+      close_preorder_db (1);
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+  preorder_insert_stmt = stmt;
+
+  return 0;
+}
+
+
+/* Insert a record into the preorder table.  The values are taken from
+   the dictionary at DICTP.  On return a SEPA-Ref value will have been
+   inserted into it; that may happen even on error.  */
+static gpg_error_t
+insert_preorder_record (keyvalue_t *dictp)
+{
+  gpg_error_t err;
+  int res;
+  keyvalue_t dict = *dictp;
+  char separef[9];
+  char *buf;
+  char datetime_buf [DB_DATETIME_SIZE];
+  int retrycount = 0;
+
+ retry:
+  make_sepa_ref (separef, sizeof separef);
+  err = keyvalue_put (dictp, "SEPA-Ref", separef);
+  if (err)
+    return err;
+  dict = *dictp;
+
+  sqlite3_reset (preorder_insert_stmt);
+
+  separef[5] = 0;
+  res = sqlite3_bind_text (preorder_insert_stmt,
+                           1, separef, -1, SQLITE_TRANSIENT);
+  if (!res)
+    res = sqlite3_bind_int (preorder_insert_stmt,
+                            2, atoi (separef + 6));
+  if (!res)
+    res = sqlite3_bind_text (preorder_insert_stmt,
+                             3, db_datetime_now (datetime_buf),
+                             -1, SQLITE_TRANSIENT);
+  if (!res)
+    res = sqlite3_bind_text (preorder_insert_stmt,
+                             4, keyvalue_get_string (dict, "Amount"),
+                             -1, SQLITE_TRANSIENT);
+  if (!res)
+    res = sqlite3_bind_text (preorder_insert_stmt,
+                             5, "EUR", -1, SQLITE_STATIC);
+  if (!res)
+    res = sqlite3_bind_text (preorder_insert_stmt,
+                             6, keyvalue_get (dict, "Desc"),
+                             -1, SQLITE_TRANSIENT);
+  if (!res)
+    res = sqlite3_bind_text (preorder_insert_stmt,
+                             7, keyvalue_get (dict, "Email"),
+                             -1, SQLITE_TRANSIENT);
+  if (!res)
+    {
+      buf = meta_field_to_string (dict);
+      if (!buf)
+        res = sqlite3_bind_null (preorder_insert_stmt, 8);
+      else
+        res = sqlite3_bind_text (preorder_insert_stmt, 8, buf, -1, es_free);
+    }
+
+  if (res)
+    {
+      log_error ("error binding a value for the preorder table: %s\n",
+                 sqlite3_errstr (res));
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  res = sqlite3_step (preorder_insert_stmt);
+  if (res == SQLITE_DONE)
+    return 0;
+
+  /* In case we hit the same primary key we need to retry.  This is
+     limited to 11000 retries (~0.1% of the primary key space).  */
+  if (res == SQLITE_CONSTRAINT_PRIMARYKEY && ++retrycount < 11000)
+    goto retry;
+
+  log_error ("error inserting into preorder table: %s (%d)\n",
+             sqlite3_errstr (res), res);
+  return gpg_error (GPG_ERR_GENERAL);
+}
+
+
+
+
+/* Create a new preorder record and store it.  Inserts a "SEPA-Ref"
+   into DICT.  */
+gpg_error_t
+preorder_store_record (keyvalue_t *dictp)
+{
+  gpg_error_t err;
+
+  err = open_preorder_db ();
+  if (err)
+    return err;
+
+  err = insert_preorder_record (dictp);
+
+  close_preorder_db (0);
+
+  return err;
+}
diff --git a/src/preorder.h b/src/preorder.h
new file mode 100644 (file)
index 0000000..83398b6
--- /dev/null
@@ -0,0 +1,26 @@
+/* preorder.h - Definition for preorder related functions
+ * Copyright (C) 2015 g10 Code GmbH
+ *
+ * This file is part of Payproc.
+ *
+ * Payproc is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Payproc is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PREORDER_H
+#define PREORDER_H
+
+gpg_error_t preorder_store_record (keyvalue_t *dictp);
+
+
+#endif /*PREORDER_H*/
diff --git a/src/t-preorder.c b/src/t-preorder.c
new file mode 100644 (file)
index 0000000..5d077dc
--- /dev/null
@@ -0,0 +1,55 @@
+/* t-preorder.c - Regression test for parts of preorder.c
+ * Copyright (C) 2015 g10 Code GmbH
+ *
+ * This file is part of Payproc.
+ *
+ * Payproc is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Payproc is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <string.h>
+#include <assert.h>
+
+#include "t-common.h"
+
+#include "preorder.c" /* The module under test.  */
+
+
+static void
+test_make_sepa_ref (void)
+{
+  char buffer[9];
+  int i;
+
+  for (i=0; i < 500; i++)
+    {
+      make_sepa_ref (buffer, sizeof buffer);
+      if (verbose)
+        printf ("%s\n", buffer);
+    }
+}
+
+
+int
+main (int argc, char **argv)
+{
+  if (argc > 1 && !strcmp (argv[1], "--verbose"))
+    verbose = 1;
+
+  test_make_sepa_ref ();
+
+  return !!errorcount;
+}