Processing Card Transactions
The API can charge the credit card already stored against a client's membership, without the card number ever being handled by your application. The stored card is identified by a token held in SunLync, and only that token is sent to the payment processor.
A complete transaction is three calls, in order: the sale is recorded, the card is charged, and the payment is recorded against the sale. The first call is what produces the receipt number that ties the other two to it.
A charge will only be accepted when all of the following are true.
If any of these is not met the request is rejected before any attempt is made to charge the card. Nothing is sent to the processor and nothing is charged.
POST /slapi/clients/inserttransaction with the client, store,
employee and a lineItems array describing what is being sold. It
responds 201 with a receiptNo. This is the receipt
number for the whole sale, and both remaining calls need it, so do this first.
POST /slapi/clients/slimcdchargecardonfile with the client,
membership, store and amount, passing the receiptNo from step 1 as
receiptNumber. The response tells you whether the card was approved
and, if so, gives you the authorisation code.
POST /slapi/clients/insertpayment with the same
receiptNumber to record how the sale was paid for.
Each object in lineItems needs a tranType, a
description, pricePerUnit, units,
amount, and tax1, tax2 and tax3.
The type decides which one further field is required.
tranType | Sells | Also requires |
|---|---|---|
M or 1 | A membership | membershipId |
P | A package | packageId |
R | A retail product | productNumber |
G | A gift certificate | giftCertNo |
insertpayment needs the receiptNumber, the
clientNumber, storecode, employeeNumber,
amount, a promoNumber, and a paymentNumber
identifying how it was paid. The payment numbers configured for the database are
returned by GET /slapi/setup/paymenttypes, so read them once and use the
one that corresponds to a credit card payment.
| Field | Description | |
|---|---|---|
clientNumber |
Required | The client whose stored card is to be charged. |
membershipId |
Required | The active EFT membership the stored card belongs to. |
storecode |
Required | The store the transaction is taken at. Its processor credentials are used. |
amount |
Required | The amount to charge. Must be greater than 0.00. |
chargeAttemptId |
Required | A value you generate that identifies this charge attempt. Up to 64 characters; a GUID is the usual choice. See Retrying safely below, which is the whole reason this field exists. |
receiptNumber |
Optional |
The receiptNo returned by inserttransaction. Passed
to the processor as the transaction reference and stored on the log entry.
Supplying it is strongly recommended: without it there is nothing tying the
charge to the sale it paid for.
|
employeeNumber |
Optional | The employee taking the transaction, recorded on the log entry. |
{
"clientNumber": "0000103018",
"membershipId": "0000004821",
"storecode": "0000000039",
"amount": "19.58",
"receiptNumber": "0018601941",
"employeeNumber": "0000007817",
"chargeAttemptId": "9f2c1ab4-7d55-4e1a-9c80-6b3f0e2d41aa"
}
| Field | Description |
|---|---|
approved | true when the card was approved, false when it was refused. |
authCode | The authorisation code, when approved. |
reference | The processor's reference for the transaction. |
responseCode | The processor's response code. |
description | The processor's description of the result. |
logId | The card processing log entry for this attempt. Quote it when querying a transaction. |
amount | The amount that was submitted. |
maskedAcctNo | The masked stored card number, so a receipt can show which card was used. |
nameOnAccount | The name held against the stored card. |
deductType | The card type of the stored card. |
replayed | false for a charge that was actually made, true when a repeated chargeAttemptId returned an earlier result instead of charging again. |
warning | Present only when the charge completed but its log entry could not be updated. See below. |
{
"approved": true,
"authCode": "008093",
"reference": "7013997703",
"responseCode": "0",
"description": "APPROVAL",
"logId": 2303358,
"amount": "19.58",
"maskedAcctNo": "XXXXXXXXXXXX9014",
"nameOnAccount": "Sample Cardholder",
"deductType": "Visa",
"replayed": false,
"message": ""
}
There are three outcomes, and they need to be handled differently.
HTTP 200 with approved set to true. The card
has been charged. Post the payment record, as in step 3.
HTTP 200 with approved set to false. The card
was declined, or the processor rejected the transaction. Nothing has been charged and
no payment should be posted. description carries the reason.
approved rather than the status code to decide whether
money moved.
HTTP 502 with chargeStateKnown set to false.
The request reached the processor but no answer came back, so whether the card was
charged is genuinely unknown. This is rare, and usually means a network fault or a
timeout.
chargeAttemptId, as described below. Do not generate a new key
for a retry: that is what turns one sale into two charges.
Generate the chargeAttemptId once, before the first attempt, and send
that same key on every retry of that charge. The API records the key against the
attempt, so a repeated key is recognised as a retry rather than a new sale.
What happens on a repeat depends on how the first attempt ended.
| First attempt | Repeat returns |
|---|---|
| Finished, approved or refused |
200 with the original result and replayed set to
true. The card is not charged again.
|
| Outcome never came back |
409. The API will not charge, because doing so could be the
second charge for one sale. Establish what happened to that attempt, using
the logId, before charging again.
|
A deliberate second charge is a different sale and needs a new key. Two charges against one receipt number, for a split payment, are two charges with two keys.
If a retry may happen after your application restarts, keep the key somewhere it survives that, alongside the pending sale rather than in memory.
Every attempt is written to the card processing log before the card is charged, and
updated with the result afterwards. If that update fails the response still reports
the charge normally and adds a warning field.
Treat this as an approved charge: the money has moved and the payment should be
posted as usual. The log entry will show the attempt without its result, so pass the
authCode and reference on to whoever reconciles the day's
transactions.
These are returned as HTTP 400 before any attempt is made to charge the
card, in the error field.
| Message ends with | Meaning |
|---|---|
| Invalid Client Number | No such client. |
| Client is not active | The client exists but is not active. |
| Invalid Membership Id | No such membership. |
| Membership is not active | The membership is not in an active state. |
| Membership does not belong to client | The membership and client do not match. |
| Invalid storecode | No such store. |
| Card on account transactions are not enabled | The store is not configured for card-on-account. |
| Customer has no billable EFT Token on file to charge | No stored card is held for the membership. |
| Customer has no unexpired EFT Token on file to charge | A card is stored but its expiry date has passed. |
| invalid amount | The amount could not be read as a number. |
| amount must be greater than 0.00 | A zero or negative amount was supplied. |
| the database needs to be updated | The database is not yet at a version that supports this endpoint. Apply the latest SunLync database update. |
A 500 is returned if the store's processor credentials cannot be read,
or if the attempt could not be written to the card processing log. In both cases
nothing has been charged.