Docs/ERP Integration

ERP Integration

Integrate Angle store credit with your ERP — accounting treatment, field mapping, implementation options, and example payloads using NetSuite as a reference.


Angle issues store credit through Shopify's native store credit system. When a customer applies store credit at checkout, Shopify records a payment transaction with the gateway shopify_store_credit. Your ERP needs to recognize this transaction and account for it correctly — as a discount to revenue, not as a payment received or a refund issued.

This guide covers the accounting principles, data flow, implementation options, and field mapping required to integrate Angle's store credit with an ERP system. NetSuite is used as the primary reference, but the same patterns apply to Dynamics 365, SAP, Sage, and other ERP platforms.


Why This Matters

Without proper ERP integration, store credit creates accounting distortions. The three most common problems:

Overstated revenue. If your ERP records the full order total as revenue and treats store credit as a payment, you're recognizing revenue on money the customer never paid. A $100 order where $20 of store credit was applied should recognize $80 of revenue, not $100.

Phantom payment methods. Some sync tools import store credit as a separate payment method alongside the credit card charge. This creates reconciliation headaches — your bank deposit doesn't match the payment total because the store credit portion was never a real cash inflow.

Broken audit trails. When store credit is manually adjusted in the ERP after the fact, the link between the Shopify transaction and the NetSuite record is lost. This makes order-level reconciliation time-consuming and error-prone during audits.

The correct treatment is straightforward: store credit is a discount. It reduces the sales price. Revenue equals the cash the customer actually paid.


Accounting Principles

Store Credit = Discount, Not Payment

When a customer applies store credit at Shopify checkout, the economic reality is that they're receiving a price reduction funded by a previous reward. The correct accounting treatment is:

ConceptCorrect TreatmentIncorrect Treatment
GL classificationDiscount line item → Discount GL accountPayment method → Cash/AR account
Revenue recognitionRevenue = Sale Price − Store CreditRevenue = Full Sale Price
Tax impactNon-taxable (store credit is not a taxable discount)N/A
Financial statementReduction to gross salesInflated gross sales with offsetting payment

Source of Truth

Shopify is the authoritative source for order totals, tax amounts, and transaction details. Your ERP should not recalculate tax or modify the order total — it should consume the Shopify data as-is and map the store credit transaction to the correct GL account.

GL Account Setup

Create a dedicated General Ledger account for store credit discounts. This account should be:

  • Type: Other Income or Discount (depending on your chart of accounts structure)
  • Name: Something descriptive like "Discount – Store Credit" or "Loyalty Reward Redemptions"
  • Tax treatment: Non-taxable
  • Linked to a non-inventory discount item (in NetSuite, this is a Discount Item configured as line-level, non-taxable)

This keeps store credit redemptions visible and reportable as their own line in your P&L, separate from promotional discounts, coupon codes, or other price reductions.


How Store Credit Appears in Shopify

When a customer applies store credit at checkout, Shopify creates an OrderTransaction object with specific properties that your integration uses to detect and process it.

Identifying Store Credit Transactions

Two fields identify a store credit transaction:

FieldValuePurpose
gatewayshopify_store_creditIdentifies the payment method as store credit
statussuccessConfirms the transaction was completed

Your integration should filter for transactions where both conditions are true. Ignore transactions with other gateway values (like shopify_payments for credit cards) — those are real payment method captures.

Example OrderTransaction Payload

This is what a store credit transaction looks like in Shopify's API response:

{
  "id": 7553485537363,
  "order_id": 5926464487507,
  "kind": "sale",
  "gateway": "shopify_store_credit",
  "status": "success",
  "amount": "18.99",
  "currency": "USD",
  "authorization": "8437104723",
  "processed_at": "2025-08-09T19:49:27-07:00",
  "receipt": {
    "account_id": 8437104723,
    "debit_operation_id": 41400402003
  }
}

The key fields for ERP integration:

  • id — The unique Shopify transaction ID. Store this in your ERP for audit trail linkage.
  • amount — The dollar amount of store credit applied. This becomes the discount line value.
  • currency — The currency of the transaction. Matches the store's configured currency.
  • kind — Usually "sale" for store credit applications at checkout.
  • processed_at — Timestamp for the transaction. Use for date matching if batching.

Multi-Transaction Orders

A single order can have multiple transactions. A typical loyalty order might look like:

  1. shopify_store_credit transaction for $18.99 (the store credit portion)
  2. shopify_payments transaction for $81.01 (the credit card portion)

Your integration should process both: the store credit transaction becomes a discount line, and the card transaction remains a standard payment.


Implementation Options

There are two approaches to integrating store credit into your ERP. The right choice depends on your existing infrastructure and technical resources.

Option A: ERP-Side Processing (SuiteScript / D365 Plugin)

The integration logic runs inside your ERP. When a Shopify order syncs, a script detects the store credit transaction and inserts the discount line automatically.

How it works with NetSuite:

A SuiteScript (User Event or Scheduled Script) processes incoming Shopify transactions. When it detects a shopify_store_credit transaction with status: success, it inserts a discount line item into the posted Cash Sale or Sales Order.

The script logic:

  1. Filter the order's transactions for gateway = shopify_store_credit and status = success.
  2. Insert a discount line item on the posted Cash Sale.
  3. Set the discount amount equal to the store credit transaction amount.
  4. Store the Shopify Transaction ID in a custom column (custcol_shopify_txn_id) for audit trail.
  5. Map the discount line to the "Discount – Store Credit" GL account.
  6. If mapping is missing or an error occurs, halt processing and log the error.

For Dynamics 365: The same pattern applies using a D365 plugin or Power Automate flow. The plugin fires on Sales Order creation, checks for store credit transactions in the synced Shopify data, and adds a discount line mapped to the appropriate GL account.

AdvantageDetail
No external dependenciesRuns entirely inside your ERP
Immediate processingUpdates the record at creation time or during post-processing
Built-in audit trailDiscount line and transaction ID live directly on the ERP record
ConsiderationDetail
Requires ERP development resourcesSuiteScript or D365 plugin developers needed for changes
Error handling is per-transactionA script error stops processing for that transaction (but logs are retained)
High-volume scalingNetSuite SuiteScript governance limits may require optimization for very high order volumes

Option B: Middleware Transformation

The integration logic runs in a middleware platform (Boomi, Celigo, Workato, custom iPaaS, or a custom integration service) that sits between Shopify and your ERP.

How it works:

  1. Retrieve data. Pull the Shopify order and its OrderTransaction objects via the Shopify API.
  2. Transform. Filter for successful store credit transactions. Create a structured "line item cache" object containing: the store credit amount, currency, the ERP's non-inventory discount Item ID, and the Shopify Transaction ID.
  3. Push update. Call the ERP's API to append the discount line to the relevant order, cash sale, or invoice.
  4. Log and retry. Store processing logs in the middleware. Retry failed updates automatically.

The middleware approach can run in two modes: real-time (processing each order as it syncs via webhook) or batch (running a scheduled job at the end of the day to correct orders before reconciliation). The batch approach is useful when your existing order sync doesn't support modification during import.

AdvantageDetail
Centralized logicTransformation rules live outside the ERP, easier to update
Better failure recoveryMiddleware retry queues handle transient ERP API failures gracefully
Dual audit trailLogs exist in both the middleware and the ERP
ERP-agnosticSame middleware logic works across NetSuite, D365, SAP, etc.
ConsiderationDetail
Additional infrastructureRequires a middleware platform (Boomi, Celigo, etc.)
Two systems to maintainTransformation logic and the ERP both need ongoing attention
Scheduling decisionMust choose between real-time webhook processing or batch correction

Which Option to Choose

FactorOption A (ERP-Side)Option B (Middleware)
You already have a middleware platformPreferred — add to existing pipeline
No middleware, simple order volumePreferred — lower complexity
Multiple ERPs or regionsHarder to maintain per-ERPCentralized logic wins
Need retry queues for reliabilityManual error handlingBuilt-in retry mechanisms
Team has SuiteScript / D365 expertiseGood fit
Want to modify during existing order syncCan intercept the sync pipeline

Field Mapping Reference (NetSuite)

This table shows how each Shopify OrderTransaction field maps to NetSuite. Adapt the NetSuite-specific field names to your ERP's equivalent.

Shopify FieldNetSuite FieldNotes
transactions[n].idCustom Subrecord: custrecord_shopify_txn_id Item Line Column: custcol_shopify_txn_idStored twice for redundancy — in the subrecord for full transaction detail and on the line item for quick lookup, reporting, and auditing
transactions[n].gatewayCustom Subrecord: custrecord_shopify_gatewayUsed for filtering — must equal shopify_store_credit
transactions[n].amountCustom Subrecord: custrecord_shopify_txn_amount Item Line Column: rateNon-taxable discount amount applied to the GL mapping
transactions[n].kindCustom Subrecord: custrecord_shopify_txn_kindUsually "sale" for store credit application
transactions[n].currencyCustom Subrecord: custrecord_shopify_txn_currencyStored for reporting and multi-currency visibility
transactions[n].statusCustom Subrecord: custrecord_shopify_txn_statusMust be "success" for processing

Why Store Twice?

The Shopify Transaction ID appears in two places: the custom subrecord (which holds the full transaction detail for reference) and the item line column (which allows quick filtering and reporting directly from transaction searches). This redundancy is intentional — it makes both detailed lookups and high-level reporting efficient without requiring subrecord joins.


NetSuite Setup Checklist

Before going live, verify these items are configured in NetSuite:

1. Create the Discount Item

  • Navigate to Lists → Accounting → Items → New → Discount
  • Name: "Store Credit Discount" (or your preferred name)
  • Configure as line-level (not subtotal-level)
  • Set as non-taxable
  • Link to your "Discount – Store Credit" GL account

2. Create the GL Account

  • Navigate to Setup → Accounting → Chart of Accounts → New
  • Name: "Discount – Store Credit"
  • Type: Other Income or Discount (per your accountant's preference)
  • Ensure it rolls up correctly in your P&L under revenue reductions

3. Create Custom Fields

  • Custom Subrecord for Shopify Transaction detail (fields listed in the mapping table above)
  • Item Line Column custcol_shopify_txn_id on Cash Sale / Sales Order / Invoice forms
  • Item Line Column rate mapped to the discount amount

4. Set Up the Script or Middleware

  • Deploy your SuiteScript (Option A) or configure your middleware pipeline (Option B)
  • Test with a small batch of orders containing store credit transactions
  • Verify the discount line appears correctly on the Cash Sale with the right GL account and amount

5. Validate Accounting

  • Run a trial batch and review the GL impact
  • Confirm revenue = sale price − store credit (not the full sale price)
  • Verify the discount amount posts to the "Discount – Store Credit" account
  • Check that the Shopify Transaction ID is stored and searchable

Adapting for Other ERPs

The NetSuite-specific field names and setup steps change per ERP, but the core pattern is identical:

Dynamics 365 Business Central / Finance & Operations:

  • Create a GL account for store credit discounts under your discount account group
  • Use a D365 plugin, Power Automate flow, or integration middleware to detect shopify_store_credit transactions
  • Map to a Sales Order discount line or a Journal Entry adjustment
  • Store the Shopify Transaction ID in a custom field for traceability

SAP Business One / S/4HANA:

  • Set up a GL account in the Chart of Accounts for loyalty discount redemptions
  • Use SAP Business One DI API or S/4HANA OData services to insert discount lines
  • Map the Shopify Transaction ID to a user-defined field (UDF) on the document

Sage Intacct / Sage 300:

  • Create an Adjustment or Discount GL account
  • Use the Sage Intacct API to modify the posted transaction with a discount line
  • Custom fields for Shopify Transaction ID storage

Regardless of the ERP, the integration follows the same five steps: detect the store credit transaction, extract the amount, create a non-taxable discount line, map to the discount GL account, and store the Shopify Transaction ID for audit trail.


Shopify API Requirements

Your integration needs the following Shopify API permissions:

Permission ScopePurpose
read_ordersAccess order data including totals, line items, and customer info
read_order_transactionsAccess the transaction objects that contain store credit details

These are standard Shopify API scopes. If you're using an existing order sync tool (like Celigo for NetSuite or a Shopify-to-D365 connector), check whether it already pulls OrderTransaction data. Some sync tools only pull the order-level payment summary and skip the detailed transaction objects — in that case, you may need a supplemental API call to get the transaction-level data.


Processing Timing

You have two timing options:

Real-time (during order sync): Process the store credit transaction and insert the discount line as part of the initial order import into the ERP. This gives you clean records from the start but requires your sync tool to support order modification during import.

Batch (end-of-day correction): Run a scheduled job before daily reconciliation that scans for orders with shopify_store_credit transactions and retroactively adds the discount line. This is simpler to implement and works with any existing order sync without modification.

Both approaches produce the same accounting result. The batch approach is more common because it doesn't require changes to the primary order sync pipeline.


Refunds and Returns

When a customer returns an order that involved store credit, the refund logic depends on your store's refund policy and Shopify's handling:

  • Shopify refunds the original payment methods pro rata. If the order was paid with $20 store credit and $80 credit card, a full refund returns $20 to store credit and $80 to the credit card.
  • Your ERP should reverse the discount line proportionally. The refund record in your ERP should include a reversal of the store credit discount so that the original discount and its reversal net to zero.
  • Partial refunds follow the same pro-rata logic. Shopify determines the split.

Monitor the Shopify Refund object's transactions for gateway: shopify_store_credit refund transactions and reverse the original discount line accordingly.


Troubleshooting

Store credit transactions aren't being detected

Verify your API call is pulling OrderTransaction objects, not just the order-level payment summary. The GET /admin/api/2024-10/orders/{order_id}/transactions.json endpoint returns the full transaction list. Check that you're filtering for gateway = shopify_store_credit (exact string match, case-sensitive).

Discount line maps to the wrong GL account

Confirm the Discount Item in NetSuite is linked to the correct GL account. If you're using SuiteScript, verify the script explicitly sets the account mapping rather than relying on the item default (which can be overridden by subsidiary or location rules).

Amounts don't reconcile between Shopify and NetSuite

Shopify reports store credit amounts in the store's presentment currency. If your ERP operates in a different base currency, ensure currency conversion is handled consistently. The transactions[n].currency field tells you which currency the amount is in.

Duplicate discount lines on the same order

Add idempotency checks to your script or middleware. Before inserting a discount line, check whether a line already exists with the same custcol_shopify_txn_id. This prevents duplicate processing during retries or reprocessing.