Finanzen: Stripe-Kunde vor der Rechnung auf die aktuellen Kundendaten bringen
Ein wiederverwendeter Stripe-Kunde (ueber das Abo oder die Metadaten gefunden) trug noch die alte E-Mail und Anschrift, die Stripe auf die Rechnung schreibt (Empfaengeranschrift, § 14 Abs. 4 Nr. 1 UStG). createProjectInvoice aktualisiert ihn jetzt nach getOrCreateStripeCustomer und vor dem ersten Rechnungsaufruf mit E-Mail, Name (Firma vor Name) und Anschrift (Land DE, wenn keine zweistellige Angabe). Scheitert das, entsteht keine Rechnung: 502 "Kundendaten konnten nicht an Stripe uebertragen werden." ohne invoiceId. Die Felder baut stripeCustomerFields fuer das Anlegen und das Aktualisieren gemeinsam. Der Stripe-Nachbau kennt POST /v1/customers/:id. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5ed4fbd188
commit
73811e0300
4 files changed
+142
-12
No files matched your search
@@ -25,6 +25,10 @@ aw.seed('customers', { $id: 'lead1', email: 'lead@example.invalid', name: 'Inter
|
||||
aw.seed('customers', { $id: 'lost1', email: 'lost@example.invalid', name: 'Verloren', customerStatus: 'Lost', status: 'active' })
|
||||
aw.seed('customers', { $id: 'act1', email: 'act@example.invalid', name: 'Aktiv', companyName: 'Aktiv GmbH', customerStatus: 'active', status: 'active' })
|
||||
|
||||
aw.seed('customers', {
|
||||
$id: 'addr1', email: 'neu@example.invalid', name: 'Anna Adresse', companyName: 'Adresse GmbH', customerStatus: 'active', status: 'active',
|
||||
street: ' Hauptstr. 1 ', postalCode: '10115', city: 'Berlin', country: 'Deutschland',
|
||||
})
|
||||
aw.seed('customers', { $id: 'noMail', name: 'Ohne Mail', customerStatus: 'active', status: 'active' })
|
||||
aw.seed('customers', { $id: 'blankMail', email: ' ', name: 'Leere Mail', customerStatus: 'active', status: 'active' })
|
||||
|
||||
@@ -187,6 +191,76 @@ test('createProjectInvoice: legt fuer Kunden ohne Stripe-Kunde einen an (Firmenn
|
||||
assert.equal(result.customerName, 'Aktiv GmbH')
|
||||
})
|
||||
|
||||
test('createProjectInvoice: ein bestehender Stripe-Kunde wird vor der Rechnung auf die aktuellen WOMS-Daten gebracht (E-Mail, Name, Anschrift)', async () => {
|
||||
// Stripe-Kunde aus frueherer Zeit (ueber die Metadaten gefunden): alte E-Mail, alter Name, alte Anschrift
|
||||
st.customers.set('cus_addr1', {
|
||||
id: 'cus_addr1', object: 'customer', name: 'Anna Alt', email: 'alt@example.invalid',
|
||||
address: { line1: 'Alte Str. 9', postal_code: '80331', city: 'München', country: 'DE' }, metadata: { webklarCustomerId: 'addr1' },
|
||||
})
|
||||
const result = await createProjectInvoice({ customerId: 'addr1', items: [BASE_ITEMS[1]], createdBy: 'k' })
|
||||
|
||||
assert.equal(callsTo('POST', '/v1/customers').length, 0, 'kein zweiter Stripe-Kunde')
|
||||
const updates = callsTo('POST', '/v1/customers/cus_addr1')
|
||||
assert.equal(updates.length, 1)
|
||||
assert.deepEqual(updates[0].body, {
|
||||
email: 'neu@example.invalid',
|
||||
name: 'Adresse GmbH',
|
||||
'address[line1]': 'Hauptstr. 1',
|
||||
'address[postal_code]': '10115',
|
||||
'address[city]': 'Berlin',
|
||||
'address[country]': 'DE',
|
||||
})
|
||||
// der Stripe-Kunde traegt danach die aktuellen Daten, die Rechnung gehoert ihm
|
||||
const stored = st.customers.get('cus_addr1')
|
||||
assert.equal(stored.email, 'neu@example.invalid')
|
||||
assert.equal(stored.name, 'Adresse GmbH')
|
||||
assert.deepEqual(stored.address, { line1: 'Hauptstr. 1', postal_code: '10115', city: 'Berlin', country: 'DE' })
|
||||
assert.equal(stored.metadata.webklarCustomerId, 'addr1')
|
||||
assert.equal(st.invoices.get(result.id).customer, 'cus_addr1')
|
||||
// zuerst der Kunde, dann die Rechnung
|
||||
const order = writes().map((c) => c.path.replace(/in_test_\d+/, ':id'))
|
||||
assert.deepEqual(order.slice(0, 2), ['/v1/customers/cus_addr1', '/v1/invoices'])
|
||||
})
|
||||
|
||||
test('createProjectInvoice: Stripe-Kunde aus dem Abo (ohne Anschrift im WOMS) bekommt E-Mail und Name, keine Anschrift; Land AT bleibt AT', async () => {
|
||||
await createProjectInvoice({ customerId: 'c1', items: [BASE_ITEMS[1]], createdBy: 'k' })
|
||||
const [update] = callsTo('POST', '/v1/customers/cus_c1')
|
||||
assert.deepEqual(update.body, { email: 'k@example.invalid', name: 'Kunde Eins' })
|
||||
assert.equal(st.customers.get('cus_c1').address ?? null, null)
|
||||
|
||||
aw.seed('customers', { $id: 'at1', email: 'at@example.invalid', name: 'Österreicher', customerStatus: 'active', status: 'active', street: 'Ring 1', postalCode: '1010', city: 'Wien', country: 'AT' })
|
||||
resetInvoiceCreateGuardForTests()
|
||||
await createProjectInvoice({ customerId: 'at1', items: [BASE_ITEMS[1]], createdBy: 'k' })
|
||||
const created = callsTo('POST', '/v1/customers').find((c) => c.body.email === 'at@example.invalid')
|
||||
assert.equal(created.body['address[country]'], 'AT', 'Anlegen und Aktualisieren benutzen dieselben Felder')
|
||||
const updated = callsTo('POST', /^\/v1\/customers\/cus_test_/).at(-1)
|
||||
assert.equal(updated.body['address[country]'], 'AT')
|
||||
assert.equal(updated.body['address[line1]'], 'Ring 1')
|
||||
})
|
||||
|
||||
test('createProjectInvoice: scheitert die Uebertragung der Kundendaten, gibt es keine Rechnung (502) und ein neuer Versuch geht', async () => {
|
||||
st.customers.set('cus_addr1', { id: 'cus_addr1', object: 'customer', name: 'Anna Alt', email: 'alt@example.invalid', address: null, metadata: { webklarCustomerId: 'addr1' } })
|
||||
st.failNext({ method: 'POST', path: '/v1/customers/cus_addr1', status: 400, message: 'geheime Stripe-Meldung' })
|
||||
await assert.rejects(
|
||||
createProjectInvoice({ customerId: 'addr1', items: [BASE_ITEMS[1]], senden: true, createdBy: 'k' }),
|
||||
(err) => {
|
||||
assert.equal(err.name, 'InvoiceError')
|
||||
assert.equal(err.status, 502)
|
||||
assert.equal(err.message, 'Kundendaten konnten nicht an Stripe übertragen werden.')
|
||||
assert.equal(err.invoiceId, undefined, 'keine Rechnung entstanden')
|
||||
assert.ok(err.cause, 'Ursache bleibt fuers Protokoll')
|
||||
return true
|
||||
}
|
||||
)
|
||||
// nichts an Rechnungen: weder Entwurf noch Posten noch Senden
|
||||
assert.equal(callsTo('POST', /^\/v1\/invoice/).length, 0)
|
||||
assert.equal(st.invoices.size, 0)
|
||||
// der Schutz vor doppeltem Anlegen ist freigegeben: derselbe Aufruf klappt, sobald Stripe wieder antwortet
|
||||
const result = await createProjectInvoice({ customerId: 'addr1', items: [BASE_ITEMS[1]], senden: true, createdBy: 'k' })
|
||||
assert.equal(result.status, 'offen')
|
||||
assert.equal(st.customers.get('cus_addr1').email, 'neu@example.invalid')
|
||||
})
|
||||
|
||||
test('createProjectInvoice: senden=true finalisiert (ohne Auto-Advance) und sendet, Rechnung ist offen mit Nummer', async () => {
|
||||
const result = await createProjectInvoice({ customerId: 'c1', items: BASE_ITEMS, daysUntilDue: 30, senden: true, createdBy: 'k' })
|
||||
|
||||
|
||||
Reference in new issue
Block a user