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:
| Concept | Correct Treatment | Incorrect Treatment |
|---|---|---|
| GL classification | Discount line item → Discount GL account | Payment method → Cash/AR account |
| Revenue recognition | Revenue = Sale Price − Store Credit | Revenue = Full Sale Price |
| Tax impact | Non-taxable (store credit is not a taxable discount) | N/A |
| Financial statement | Reduction to gross sales | Inflated 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:
| Field | Value | Purpose |
|---|---|---|
gateway | shopify_store_credit | Identifies the payment method as store credit |
status | success | Confirms 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:
shopify_store_credittransaction for $18.99 (the store credit portion)shopify_paymentstransaction 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:
- Filter the order's transactions for
gateway = shopify_store_creditandstatus = success. - Insert a discount line item on the posted Cash Sale.
- Set the discount amount equal to the store credit transaction amount.
- Store the Shopify Transaction ID in a custom column (
custcol_shopify_txn_id) for audit trail. - Map the discount line to the "Discount – Store Credit" GL account.
- 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.
| Advantage | Detail |
|---|---|
| No external dependencies | Runs entirely inside your ERP |
| Immediate processing | Updates the record at creation time or during post-processing |
| Built-in audit trail | Discount line and transaction ID live directly on the ERP record |
| Consideration | Detail |
|---|---|
| Requires ERP development resources | SuiteScript or D365 plugin developers needed for changes |
| Error handling is per-transaction | A script error stops processing for that transaction (but logs are retained) |
| High-volume scaling | NetSuite 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:
- Retrieve data. Pull the Shopify order and its
OrderTransactionobjects via the Shopify API. - 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.
- Push update. Call the ERP's API to append the discount line to the relevant order, cash sale, or invoice.
- 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.
| Advantage | Detail |
|---|---|
| Centralized logic | Transformation rules live outside the ERP, easier to update |
| Better failure recovery | Middleware retry queues handle transient ERP API failures gracefully |
| Dual audit trail | Logs exist in both the middleware and the ERP |
| ERP-agnostic | Same middleware logic works across NetSuite, D365, SAP, etc. |
| Consideration | Detail |
|---|---|
| Additional infrastructure | Requires a middleware platform (Boomi, Celigo, etc.) |
| Two systems to maintain | Transformation logic and the ERP both need ongoing attention |
| Scheduling decision | Must choose between real-time webhook processing or batch correction |
Which Option to Choose
| Factor | Option A (ERP-Side) | Option B (Middleware) |
|---|---|---|
| You already have a middleware platform | — | Preferred — add to existing pipeline |
| No middleware, simple order volume | Preferred — lower complexity | — |
| Multiple ERPs or regions | Harder to maintain per-ERP | Centralized logic wins |
| Need retry queues for reliability | Manual error handling | Built-in retry mechanisms |
| Team has SuiteScript / D365 expertise | Good fit | — |
| Want to modify during existing order sync | — | Can 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 Field | NetSuite Field | Notes |
|---|---|---|
transactions[n].id | Custom Subrecord: custrecord_shopify_txn_id Item Line Column: custcol_shopify_txn_id | Stored twice for redundancy — in the subrecord for full transaction detail and on the line item for quick lookup, reporting, and auditing |
transactions[n].gateway | Custom Subrecord: custrecord_shopify_gateway | Used for filtering — must equal shopify_store_credit |
transactions[n].amount | Custom Subrecord: custrecord_shopify_txn_amount Item Line Column: rate | Non-taxable discount amount applied to the GL mapping |
transactions[n].kind | Custom Subrecord: custrecord_shopify_txn_kind | Usually "sale" for store credit application |
transactions[n].currency | Custom Subrecord: custrecord_shopify_txn_currency | Stored for reporting and multi-currency visibility |
transactions[n].status | Custom Subrecord: custrecord_shopify_txn_status | Must 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_idon Cash Sale / Sales Order / Invoice forms - Item Line Column
ratemapped 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_credittransactions - 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 Scope | Purpose |
|---|---|
read_orders | Access order data including totals, line items, and customer info |
read_order_transactions | Access 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.
Related Documentation
- How Angle Works — Architecture overview and why Angle uses native store credit
- Cashback Rewards — How store credit is earned on purchases
- Multi-Currency Support — Currency handling across store instances
- Shopify Flow Integration — Custom action rewards that issue store credit via Flow