Webhook for Payment Notification

With new regulations being introduced in Spain, we will need to integrate a notification system (via REST API) when a payment is made—this is called Verifactu. This system is already in place in some regions (the interested hub is located in a region where this system has already been implemented) and has been approved by the EU, meaning it will likely be adopted in other countries in the coming years.

To prepare for this, it would be highly practical to have a webhook trigger when a payment is made (or when an invoice is generated—open to discussion). This would allow us to execute a script or workflow (such as with n8n) to make the relevant API call.

The information required each time an order is paid would include the total price, items, and time of the transaction.

My question is whether this feature could be considered by the global community. If it is feasible, we may be able to allocate some budget to support its development.

Thank you for bringing this up.

This request is similar to another webhook that was requested:

The estimate there was 1.5 days dev time. The same applies here. For funding, we usually multiply this by a certain factor to account for review, testing, project management and contribution to maintenance. @Rachel, what’s that factor?

this is a complete stab in the dark but I would suggest a factor of x3

thanks @NickWeir !

@maikel I’m noticing you are talking about 1.5 days here but mentioned 1.5 hours on slack. Which is the good estimate?

@mikel I don’t think it’s a good move to rely on the invoice as current OFN invoices are made on the fly and are not stored (so can be created multiple times).

Can we get into more detail on the type of data needed? A few questions that came in for me:

  • what do you need when you mention “items”: name + unit + quantity sold?
  • what is the time of the transaction for you? time of payment or order completion date? If it is the time of payment, what is the expected behavior when the payment is made later in cash?
  • for price, do you need VAT breakdown?

thanks!

Oh dear, that’s a bad mistake. 1.5 days is the right one.

Hi @Rachel,

Here are the answers to your questions:

  • Yes, the information for the items includes description, quantity, price per unit, and VAT category.
  • The timing for the transaction should occur right after the payment is completed. I believe the transition from “balance due” to “paid” is the appropriate moment to trigger it, this should be adequate for all kind of payments. What do you think?
  • We do not need this, as we already have all the necessary information from the VAT category of the items.

I can share with you the json object that I need to generate if this would more clear

@mikel when dealing with cash payments, this change of payment state can occur very late in the process (only when payments are captured). Will this be a problem here?

I can share with you the json object that I need to generate if this would more clear

that’d be awesome yes :+1:

Hi @Rachel ,
Yes, you are right. When paying with cash, the timing can be delayed due to the nature of this type of payment. However, I don’t see any issue, as this is the moment when the transaction is expected to be reflected in the system and the info is ready to be submitted. Are you aware of any potential issues that I might be missing?

This is the JSON file we would need to provide. Let me know if you need any clarification:

{
“fecha”: “25/09/2024”,
“hora”: “12:45:00”,
“nif”: “XXXXXXXXX”,
“simplificada”: false,
“pais_cliente”: “ES”,
“nombre”: “My Coop”,
“direccion”: “Gasteiz”,
“cp”: “01013”,
“serie”: “A”,
“numero”: “00003”,
“rectificativa”: false,
“clave_rectificativa”: “R1”,
“tipo_rectificativa”: “I”,
“rectificadas”: [ ],
“tipo_operacion”: “servicios”,
“intracomunitaria”: false,
“exportacion”: false,
“retencion”: 0,
“lineas”: [
{
“descripcion”: “product1”,
“cantidad”: 1,
“importe_unitario”: 23.2,
“tipo_iva”: 0,
“tipo_req”: 0
},{
“descripcion”: “product2”,
“cantidad”: 2,
“importe_unitario”: 2,
“tipo_iva”: 0,
“tipo_req”: 0
}
],
“total_factura”: 27.2,
“zuzendu”: false
}

Thanks Mikel. I’m thinking about the balance between creating something useful for your purpose, and potentially useful for other purposes.

You mentioned that you would use this webhook to execute a script or workflow (such as with n8n) to make the relevant API call. So given that you will have the ability to transform the data I’m wondering if we can create a more general format for this webhook, using OFN terms that are more closely aligned with the code (eg enterprise_name instead of nombre). This way it would be more helpful when implementing the system in other countries.

Would that be ok with you?

Either way, to provide the quote we will need to have a clear understanding of what data is required. Can you please annotate your suggested JSON file to describe where the data for each line comes from? You can use OFN database column names, or screenshots from the web interface.

Thanks for the feedback. Here the annotated json. The * indicates the values that need to be sent from OFN:

{
“fecha”: “25/09/2024”, //date, basically today. Not required that it comes from the webhook, it can be done in script or n8n
“hora”: “12:45:00”, //time, basically time. Not required that it comes from the webhook, it can be done in script or n8n
“nif”: “XXXXXXXXX”, // * the business number, ABN in DB
“simplificada”: false, // not related to OFN
“pais_cliente”: “ES”, // that can be hardcoded in the script/n8n
“nombre”: “My Coop”, // * enterprise_name
“direccion”: “Gasteiz”, // * enterprise_address
“cp”: “01013”, // * enterprise_zip_code
“serie”: “A”, // Not required in the webhook. That can be generated in the script/n8n
“numero”: “00003”, // Not required in the webhook. That can be generated in the script/n8n
“rectificativa”: false, //Not required in the webhook.
“clave_rectificativa”: “R1”, //Not required in the webhook.
“tipo_rectificativa”: “I”, // Not required in the webhook.
“rectificadas”: , //Not required in the webhook.
“tipo_operacion”: “servicios”, //Not required in the webhook.
“intracomunitaria”: false, //Not required in the webhook.
“exportacion”: false, //Not required in the webhook.
“retencion”: 0, // Not required in the webhook.
“lineas”: [
{
“descripcion”: “product1”, // * product_name
“cantidad”: 1, // * quantity
“importe_unitario”: 23.2, // * price_per_unit
“tipo_iva”: 0, // * tax type
“tipo_req”: 0 // not applicable
},{
“descripcion”: “product2”, // * product_name
“cantidad”: 2, // * quantity
“importe_unitario”: 2, // * price_per_unit
“tipo_iva”: 0, // * tax_type
“tipo_req”: 0 // not applicable
}
],
“total_factura”: 27.2, // order_total_price to pay
“zuzendu”: false // not applicable
}

To make this webhook more versatile, I suggest triggering it not only when a payment changes from ‘balance due’ to ‘paid,’ but on any status change. Additionally, the updated status should be included to ensure the ability to react appropriately. This approach would enable responses to all status updates.

Thanks for that, and sorry for the delay. I had a go at drafting a data structure. As usual, it’s more complicated than first glance!

How does this look? I started mapping it direct to database values (ignore the format, it would JSON in the end) :

spree_payment:
  updated_at: "2024-12-10 00:00:00" # ISO or similar format
  amount: 1.00 # could it be a partial payment made by admin?
  state: "completed" # other states are mentioned at https://github.com/openfoodfoundation/openfoodnetwork/blob/master/app/models/spree/payment.rb
enterprise:
  abn: "XXXXXXXXX"
  acn: "XXXXXXXXX"
  name: "My Coop"
  address:
    address1: ""
    address2: ""
    city: ""
    zipcode: ""
spree_order:
  total: 1.00 
  currency: ""
line_items:
  - quantity: 1
    price: 1.00 
    tax_category_name: "VAT"

But for the line item name and units, it gets complicated…

    spree_product:
      name: "" # or maybe some other variation. line item names are a bit complicated
    spree_variant:
      # units are complicated.. this is kilograms
      variant_unit: "weight" # weight/volume/items
      variant_unit_scale: 1000  # (base scale for weight is grams. base scale for volume is litres I think)
      # So this means 2 kilograms:
      unit_value: 2

You probably don’t need that, instead you need what shows on the invoice. So we can simplify that. Maybe more like this:

    product_name: "Apples"
    name_to_display: "Fuji" #could be blank, or coudl duplicate product_name
    unit_to_display: "2kg"

I think I would estimate 2 days dev for this.

I haven’t investigated how to time the webhooks, but I think it should be ok to trigger for any state change, then just let Mikel filter out which ones he needs.

I had a look and I think this would cover the need. As you mention, the requirement is closer to the invoice type of info. Thanks a lot for this

As for webhook timing, the solution you suggest is the one I would choose. Trigger it every time there is a state change and filter the events that are relevant to the usecase.

Which are the next steps and how I can help to make it happen?

I think the next steps include:

  • Create a new feature issue with the relevant information (high level requirements and suggested solution)
  • Confirm the dev estimate (I estimate 2 days, but we can review this once the issue is filled out)
  • Confirm the cost based on this
  • Confirm funding

@Rachel does this sound right, and who should create the issue?

@dcook sorry I was syncing with finance circle in parallel and didn’t answered here.

I think the best is to first get the quote out based on this estimate so Mikel can find funding. We are trying to setup a better process than we had until now, that’s why it’s taking a long time @mikel . Sorry about that, I hope we can still get a first estimate before xmas.

Ok, let me know if I could be of any help in this process

@mikel so after lots of iteration we land on a first estimate at 5760 euros. This includes the estimation of devs days, code review, product and testing + a contribution to OFN global as OFN would also have to maintain this in the future.

Can you tell me if this sounds reasonable for you / Do you think it will be ok to find this funding?
We can have a call next week if you feel this is easier. Let me know on slack.
Also: do you need a formal document with that number or is this post enough for now?