Convert GOBL documents to and from the Stripe API format.
Invoice:
package main
import (
"fmt"
"os"
"json"
goblstripe "github.com/invopop/gobl.stripe"
"github.com/invopop/gobl/uuid"
"github.com/stripe/stripe-go/v81"
)
const (
//namespace used to give a significant uuid to the invoice
namespace = "550e8400-e29b-41d4-a716-446655440000"
)
func main{
data, _ := os.ReadFile("examples/stripe.gobl/stripe_basic_invoice.json")
s := new(stripe.Invoice)
if err := json.Unmarshal(data, s); err != nil {
fmt.Errorf("failed to unmarshal JSON: %w", err)
}
gi, err := goblstripe.FromInvoice(s, uuid.MustParse(namespace))
if err != nil {
fmt.Errorf("error in conversion: %w", err)
}
// Now you can use the GOBL invoice (*bill.Invoice object) returned.
}
Credit Note:
package main
import (
"fmt"
"os"
"json"
goblstripe "github.com/invopop/gobl.stripe"
"github.com/invopop/gobl/uuid"
"github.com/stripe/stripe-go/v81"
)
const (
//namespace used to give a significant uuid to the invoice
namespace = "550e8400-e29b-41d4-a716-446655440000"
)
func main{
data, _ := os.ReadFile("examples/stripe.gobl/stripe_basic_invoice.json")
s := new(stripe.CreditNote)
if err := json.Unmarshal(data, s); err != nil {
fmt.Errorf("failed to unmarshal JSON: %w", err)
}
gi, err := goblstripe.FromCreditNote(s, uuid.MustParse(namespace))
if err != nil {
fmt.Errorf("error in conversion: %w", err)
}
// Now you can use the GOBL invoice (*bill.Invoice object) returned.
}
Coming soon ...
The GOBL <-> Stripe package also includes a command line helper. You can install it manually in your Go environment (from this main directory) with:
go install ./cmd/gobl.tin
Coming soon ...
With the revert
command you will be able to:
- Listen to Stripe events (e.g., invoice finalized or credit note created).
- Convert the event data into GOBL format.
- Save the output as a JSON file.
To test this functionality locally, use the Stripe CLI, which you can install by following these instructions.
Choose a port to forward events to (e.g., port 5276
in this example), and run the following command:
stripe listen --forward-to localhost:5276/webhook
Before running the revert
command, you need to configure 2 secrets: Stripe secret API key and the webhook secret. This can be done in 2 ways:
- With environment variables: You would need to set the
STRIPE_SECRET_KEY
andSTRIPE_WEBHOOK_SECRET
on your environment. Then you run the command like:
gobl.stripe revert -p 5276
- You can pass the secrets to the command directly:
gobl.stripe revert -p 5276 -k sk_test_afjsadf44332... -s whsec_jdferfwerif329...
To test, trigger a Stripe event. You can do these in several ways:
- From the command line
For example, to trigger an
invoice.finalized
event, run:
stripe trigger invoice.finalized
This will generate the most basic type of invoice.
- From the Stripe Dashboard
You can manually create and finalize invoices from the Stripe Dashboard.
- From the Stripe API
You can also create and finalize invoices using the Stripe API.
Once an event is triggered, the revert
command generates 2 JSON files in the same directory:
stripe_{id}.json
: Contains the Stripe event datagobl_{id}.json
: Contains the corresponding GOBL-converted data.
All method or definition names in the goblstripe
package should primarily reference the Stripe API objects that will be generated or parsed, suffixed with the verb representing the intent, e.g.:
ToCustomer
implies that a GOBL object, defined in the method, will be converted into a stripe customer.FromCustomer
expects a stripe object to be converted into GOBL.ToInvoice
converts a GOBL Invoice into the Stripe equivalent.FromInvoice
expects a stripe API object to convert into GOBL.ToTaxID
converts a GOBLtax.Identity
ororg.Identity
into the expected Stripe customer Tax ID object.
In some cases, a new GOBL object is created that does not correspond to any specific or similar object in Stripe. For such cases, the method names include the prefix new
, e.g.:
newOrdering
creates a newOrdering
object not from a similar object but from the wholeInvoice
.
To get all the required information, they request to the invoice must be done expanding the following fields.
- account_tax_ids
- customer.tax_ids
- lines.data.discounts
- lines.data.tax_amounts.tax_rate
- total_tax_amounts.tax_rate
- payment_intent
- invoice.account_tax_ids
- customer.tax_ids
- lines.data.tax_amounts.tax_rate
For the invoice supplier we are currently assuming the following:
- The
supplier
is always the Stripeaccount
. This is false if using Stripe connect. The fieldissuer
informs you if the issuer is the own account (self
) or another account (account
).- If the
issuer
is notself
we don't have access to the tax ids as they are not displayed on theaccount
object.
- If the
- The
supplier
has exactly 1 tax id. In Stripe, the account can have several tax ids or none. If it has several, we assume that the first one is the correct one. If it has none, we would need to get an Invopop supplier like in Chargebee.
A solution for these problems could be directly getting the supplier from Invopop like Chargebee does.
- We must add more values for the tax ids mapping from Stripe to GOBL.
Tax is included/excluded is treated differently in GOBL and Stripe. In GOBL, the included flag is for all the taxes in the invoice, while in Stripe it is considered that the invoice can have several taxes. Our assumption is that there is going to be only 1 tax type (VAT, SGT, ...) per invoice.
For the moment, we consider there are no discounts on the general invoice, but only on the line items.
- For the moment, we are not including the payment instructions for already paid invoices. We could add it by expanding the
payment_method
field incharge
. - For the advances there is no a straightforward way to get them as another API request is required. Currently we are handling it as a unique advancement on the
amount_paid
.
- Proration
- Charges (*bill.Charge). If needed for delivering goods we could add the shipping charges.
For tags and more we can use the metadata
or custom_fields
field
We still need to define what to do with the naming of fields and tags and where to include them. My hypothesis is that we can create the app in Stripe and add the fields there.
For the moment GOBL fields $addons
and $tags
are not being mapped as we need to have the complete integration with Stripe to understand how can we pass these fields. Some ideas are to include them on the metadata
field that several Stripe fields have. We could use the Invoice templates
to define some common custom fields
per invoice.
livemode
field states wether the generated invoice is in testing or live.True
means it is live andFalse
testing. Currently not being used.-
- For tax there is a field that is default_tax_rates, but it is normally empty as not specified by the user. To check the rates we need to check the total_tax_amounts.
- For the
regime
, theaccount_country
is always used. - We assume the attribute
has_more
in lines is false. This attribute is used to state if there are more line pages in the invoice that we can fetch. - Amount is always charged in the smallest possible unit (cents in euros, yens in yens, ...)
When converting from Stripe to GOBL, the Stripe invoice does not include any exchange rates. We add some default ones. To have updated ones you should include the exchange rates step in the workflow.